From 51b65ec4372dcbbd98abf660cf9fbd87f8919c88 Mon Sep 17 00:00:00 2001 From: Tindy X <49061470+tindy2013@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:23:43 +0800 Subject: [PATCH] Enhancements Fix base sing-box configuration. Add support for adding Clash modes to sing-box configs. Optimize codes. --- base/base/all_base.tpl | 134 ++++++++++++++++++++++----- base/base/singbox.json | 128 ++++++++++++++++++++----- base/pref.example.ini | 5 + base/pref.example.toml | 11 +++ base/pref.example.yml | 3 + src/generator/config/ruleconvert.cpp | 10 +- src/generator/config/subexport.cpp | 106 ++++++++++++--------- src/handler/settings.cpp | 5 +- src/handler/settings.h | 2 +- 9 files changed, 306 insertions(+), 98 deletions(-) diff --git a/base/base/all_base.tpl b/base/base/all_base.tpl index e3eb53189..933e8cae2 100644 --- a/base/base/all_base.tpl +++ b/base/base/all_base.tpl @@ -269,30 +269,116 @@ enhanced-mode-by-rule = true {% if request.target == "singbox" %} { - "log": { - "disabled": false, - "level": "info", - "output": "box.log", - "timestamp": true - }, - "dns": {}, - "ntp": { - "enabled": false, - "server": "time.apple.com", - "server_port": 123, - "interval": "30m" - }, - "inbounds": [ - { - "type": "socks", - "tag": "socks-in", - "listen": "127.0.0.1", - "listen_port": 2080 - } - ], - "outbounds": [], - "route": {}, - "experimental": {} + "log": { + "disabled": false, + "level": "info", + "timestamp": true + }, + "dns": { + "servers": [ + { + "tag": "dns_proxy", + "address": "tls://1.1.1.1", + "address_resolver": "dns_resolver" + }, + { + "tag": "dns_direct", + "address": "h3://dns.alidns.com/dns-query", + "address_resolver": "dns_resolver", + "detour": "DIRECT" + }, + { + "tag": "dns_fakeip", + "address": "fakeip" + }, + { + "tag": "dns_resolver", + "address": "223.5.5.5", + "detour": "DIRECT" + }, + { + "tag": "block", + "address": "rcode://success" + } + ], + "rules": [ + { + "outbound": [ + "any" + ], + "server": "dns_resolver" + }, + { + "geosite": [ + "category-ads-all" + ], + "server": "dns_block", + "disable_cache": true + }, + { + "geosite": [ + "geolocation-!cn" + ], + "query_type": [ + "A", + "AAAA" + ], + "server": "dns_fakeip" + }, + { + "geosite": [ + "geolocation-!cn" + ], + "server": "dns_proxy" + } + ], + "final": "dns_direct", + "independent_cache": true, + "fakeip": { + "enabled": true, + {% if default(request.singbox.ipv6, "") == "1" %} + "inet6_range": "fc00::\/18", + {% endif %} + "inet4_range": "198.18.0.0\/15" + } + }, + "ntp": { + "enabled": true, + "server": "time.apple.com", + "server_port": 123, + "interval": "30m", + "detour": "DIRECT" + }, + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + {% if bool(default(global.singbox.allow_lan, "")) %} + "listen": "0.0.0.0", + {% else %} + "listen": "127.0.0.1", + {% endif %} + "listen_port": {{ default(global.singbox.mixed_port, "2080") }} + }, + { + "type": "tun", + "tag": "tun-in", + "inet4_address": "172.19.0.1/30", + {% if default(request.singbox.ipv6, "") == "1" %} + "inet6_address": "fdfe:dcba:9876::1/126", + {% endif %} + "auto_route": true, + "strict_route": true, + "stack": "mixed", + "sniff": true + } + ], + "outbounds": [], + "route": { + "rules": [], + "auto_detect_interface": true + }, + "experimental": {} } {% endif %} diff --git a/base/base/singbox.json b/base/base/singbox.json index 4068bfab9..4263bd55d 100644 --- a/base/base/singbox.json +++ b/base/base/singbox.json @@ -1,26 +1,104 @@ { - "log": { - "disabled": false, - "level": "info", - "output": "box.log", - "timestamp": true - }, - "dns": {}, - "ntp": { - "enabled": false, - "server": "time.apple.com", - "server_port": 123, - "interval": "30m" - }, - "inbounds": [ - { - "type": "socks", - "tag": "socks-in", - "listen": "127.0.0.1", - "listen_port": 2080 - } - ], - "outbounds": [], - "route": {}, - "experimental": {} -} \ No newline at end of file + "log": { + "disabled": false, + "level": "info", + "timestamp": true + }, + "dns": { + "servers": [ + { + "tag": "dns_proxy", + "address": "tls://1.1.1.1", + "address_resolver": "dns_resolver" + }, + { + "tag": "dns_direct", + "address": "h3://dns.alidns.com/dns-query", + "address_resolver": "dns_resolver", + "detour": "DIRECT" + }, + { + "tag": "dns_fakeip", + "address": "fakeip" + }, + { + "tag": "dns_resolver", + "address": "223.5.5.5", + "detour": "DIRECT" + }, + { + "tag": "block", + "address": "rcode://success" + } + ], + "rules": [ + { + "outbound": [ + "any" + ], + "server": "dns_resolver" + }, + { + "geosite": [ + "category-ads-all" + ], + "server": "dns_block", + "disable_cache": true + }, + { + "geosite": [ + "geolocation-!cn" + ], + "query_type": [ + "A", + "AAAA" + ], + "server": "dns_fakeip" + }, + { + "geosite": [ + "geolocation-!cn" + ], + "server": "dns_proxy" + } + ], + "final": "dns_direct", + "independent_cache": true, + "fakeip": { + "enabled": true, + "inet6_range": "fc00::\/18", + "inet4_range": "198.18.0.0\/15" + } + }, + "ntp": { + "enabled": true, + "server": "time.apple.com", + "server_port": 123, + "interval": "30m", + "detour": "DIRECT" + }, + "inbounds": [ + { + "type": "mixed", + "tag": "mixed-in", + "listen": "0.0.0.0", + "listen_port": 2080 + }, + { + "type": "tun", + "tag": "tun-in", + "inet4_address": "172.19.0.1/30", + "inet6_address": "fdfe:dcba:9876::1/126", + "auto_route": true, + "strict_route": true, + "stack": "mixed", + "sniff": true + } + ], + "outbounds": [], + "route": { + "rules": [], + "auto_detect_interface": true + }, + "experimental": {} +} diff --git a/base/pref.example.ini b/base/pref.example.ini index 7ca2a4cfb..6029e18a4 100644 --- a/base/pref.example.ini +++ b/base/pref.example.ini @@ -114,6 +114,9 @@ clash_use_new_field_name=true ; key: value clash_proxies_style=flow +;add Clash mode to sing-box rules, and add a GLOBAL group to end of outbounds +singbox_add_clash_modes=true + ;Rename remarks with the following patterns. Supports regular expression. ;Format: Search_Pattern@Replace_Pattern ;rename_node=IPLC@专线 @@ -226,6 +229,8 @@ clash.http_port=7890 clash.socks_port=7891 clash.allow_lan=true clash.log_level=info +singbox.allow_lan=true +singbox.mixed_port=2080 [aliases] ;Aliases for accessing interfaces. Can be used to shorten the URI. diff --git a/base/pref.example.toml b/base/pref.example.toml index a8bf398d6..9a486d969 100644 --- a/base/pref.example.toml +++ b/base/pref.example.toml @@ -140,6 +140,9 @@ clash_use_new_field_name = true # key: value clash_proxies_style = "flow" +# add Clash mode to sing-box rules, and add a GLOBAL group to end of outbounds +singbox_add_clash_modes = true + [[node_pref.rename_node]] match = '\(?((x|X)?(\d+)(\.?\d+)?)((\s?倍率?)|(x|X))\)?' replace = "$1x" @@ -237,6 +240,14 @@ value = "true" key = "clash.log_level" value = "info" +[[template.globals]] +key = "singbox.allow_lan" +value = "true" + +[[template.globals]] +key = "singbox.mixed_port" +value = "2080" + [[aliases]] uri = "/clash" target = "/sub?target=clash" diff --git a/base/pref.example.yml b/base/pref.example.yml index f9b6c3d51..b7df31297 100644 --- a/base/pref.example.yml +++ b/base/pref.example.yml @@ -49,6 +49,7 @@ node_pref: append_sub_userinfo: true clash_use_new_field_name: true clash_proxies_style: flow + singbox_add_clash_modes: true rename_node: # - {match: "\\(?((x|X)?(\\d+)(\\.?\\d+)?)((\\s?倍率?)|(x|X))\\)?", replace: "$1x"} # - {script: "function rename(node){}"} @@ -106,6 +107,8 @@ template: - {key: clash.socks_port, value: 7891} - {key: clash.allow_lan, value: true} - {key: clash.log_level, value: info} + - {key: singbox.allow_lan, value: true} + - {key: singbox.mixed_port, value: 2080} aliases: - {uri: /v, target: /version} diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp index 543cec488..256f4dfa5 100644 --- a/src/generator/config/ruleconvert.cpp +++ b/src/generator/config/ruleconvert.cpp @@ -487,7 +487,7 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector& allocator = base_rule.GetAllocator(); + auto &allocator = base_rule.GetAllocator(); rapidjson::Value rules(rapidjson::kArrayType); if (!overwrite_original_rules) @@ -496,6 +496,14 @@ void rulesetToSingBox(rapidjson::Document &base_rule, std::vector global.maxAllowedRules) diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp index f47f4bf15..da4356132 100644 --- a/src/generator/config/subexport.cpp +++ b/src/generator/config/subexport.cpp @@ -155,29 +155,29 @@ bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy & return true; } -void processRemark(std::string &oldremark, std::string &newremark, string_array &remarks_list, bool proc_comma = true) +void processRemark(std::string &remark, string_array &remarks_list, bool proc_comma = true) { // Replace every '=' with '-' in the remark string to avoid parse errors from the clients. // Surge is tested to yield an error when handling '=' in the remark string, // not sure if other clients have the same problem. - std::replace(oldremark.begin(), oldremark.end(), '=', '-'); + std::replace(remark.begin(), remark.end(), '=', '-'); if(proc_comma) { - if(oldremark.find(',') != std::string::npos) + if(remark.find(',') != std::string::npos) { - oldremark.insert(0, "\""); - oldremark.append("\""); + remark.insert(0, "\""); + remark.append("\""); } } - newremark = oldremark; + std::string tempRemark = remark; int cnt = 2; - while(std::find(remarks_list.begin(), remarks_list.end(), newremark) != remarks_list.end()) + while(std::find(remarks_list.begin(), remarks_list.end(), tempRemark) != remarks_list.end()) { - newremark = oldremark + " " + std::to_string(cnt); + tempRemark = remark + " " + std::to_string(cnt); cnt++; } - oldremark = newremark; + remark = tempRemark; } void groupGenerate(const std::string &rule, std::vector &nodelist, string_array &filtered_nodelist, bool add_direct, extra_settings &ext) @@ -241,18 +241,18 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr YAML::Node singleproxy; std::string type = getProxyTypeName(x.Type); - std::string remark, pluginopts = replaceAllDistinct(x.PluginOption, ";", "&"); + std::string pluginopts = replaceAllDistinct(x.PluginOption, ";", "&"); if(ext.append_proxy_type) x.Remark = "[" + type + "] " + x.Remark; - processRemark(x.Remark, remark, remarks_list, false); + processRemark(x.Remark, remarks_list, false); tribool udp = ext.udp; tribool scv = ext.skip_cert_verify; udp.define(x.UDP); scv.define(x.AllowInsecure); - singleproxy["name"] = remark; + singleproxy["name"] = x.Remark; singleproxy["server"] = x.Hostname; singleproxy["port"] = x.Port; @@ -471,7 +471,7 @@ void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const ProxyGr else singleproxy.SetStyle(YAML::EmitterStyle::Flow); proxies.push_back(singleproxy); - remarks_list.emplace_back(std::move(remark)); + remarks_list.emplace_back(x.Remark); nodelist.emplace_back(x); } @@ -657,14 +657,13 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf for(Proxy &x : nodes) { - std::string remark; if(ext.append_proxy_type) { std::string type = getProxyTypeName(x.Type); x.Remark = "[" + type + "] " + x.Remark; } - processRemark(x.Remark, remark, remarks_list); + processRemark(x.Remark, remarks_list); std::string &hostname = x.Hostname, &username = x.Username, &password = x.Password, &method = x.EncryptMethod, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &edge = x.Edge, &path = x.Path, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam, &plugin = x.Plugin, &pluginopts = x.PluginOption; std::string port = std::to_string(x.Port); @@ -834,13 +833,13 @@ std::string proxyToSurge(std::vector &nodes, const std::string &base_conf proxy += ", udp-relay=" + udp.get_str(); if(ext.nodelist) - output_nodelist += remark + " = " + proxy + "\n"; + output_nodelist += x.Remark + " = " + proxy + "\n"; else { - ini.set("{NONAME}", remark + " = " + proxy); + ini.set("{NONAME}", x.Remark + " = " + proxy); nodelist.emplace_back(x); } - remarks_list.emplace_back(std::move(remark)); + remarks_list.emplace_back(x.Remark); } if(ext.nodelist) @@ -1083,15 +1082,13 @@ void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { std::string type; - std::string remark; std::string proxyStr; tribool udp, tfo, scv, tls13; std::vector nodelist; @@ -1324,7 +1320,7 @@ void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_con void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const ProxyGroupConfigs &extra_proxy_group, extra_settings &ext) { std::string proxy; - std::string remark, username, password, method; + std::string username, password, method; std::string plugin, pluginopts; std::string id, aid, transproto, faketype, host, path, quicsecure, quicsecret, tlssecure; std::string url; @@ -1679,7 +1675,7 @@ void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, INIReader &ini, std::vector &nodes, const std::string &base_conf, std::string type = getProxyTypeName(x.Type); x.Remark = "[" + type + "] " + x.Remark; } - std::string remark = x.Remark; - processRemark(x.Remark, remark, remarks_list); + processRemark(x.Remark, remarks_list); std::string &hostname = x.Hostname, &username = x.Username, &password = x.Password, &method = x.EncryptMethod, &plugin = x.Plugin, &pluginopts = x.PluginOption, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &path = x.Path, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam; std::string port = std::to_string(x.Port), aid = std::to_string(x.AlterId); @@ -1931,12 +1926,12 @@ std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, if(ext.nodelist) - output_nodelist += remark + " = " + proxy + "\n"; + output_nodelist += x.Remark + " = " + proxy + "\n"; else { - ini.set("{NONAME}", remark + " = " + proxy); + ini.set("{NONAME}", x.Remark + " = " + proxy); nodelist.emplace_back(x); - remarks_list.emplace_back(std::move(remark)); + remarks_list.emplace_back(x.Remark); } } @@ -2096,6 +2091,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v rapidjson::Document::AllocatorType &allocator = json.GetAllocator(); rapidjson::Value outbounds(rapidjson::kArrayType), route(rapidjson::kArrayType); std::vector nodelist; + string_array remarks_list; auto direct = buildObject(allocator, "type", "direct", "tag", "DIRECT"); outbounds.PushBack(direct, allocator); @@ -2108,6 +2104,8 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v if (ext.append_proxy_type) x.Remark = "[" + type + "] " + x.Remark; + processRemark(x.Remark, remarks_list, false); + tribool udp = ext.udp, tfo = ext.tfo, scv = ext.skip_cert_verify; udp.define(x.UDP); tfo.define(x.TCPFastOpen); @@ -2118,7 +2116,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v { case ProxyType::Shadowsocks: { - addSingBoxCommonMembers(proxy, x, "ss", allocator); + addSingBoxCommonMembers(proxy, x, "shadowsocks", allocator); proxy.AddMember("method", rapidjson::StringRef(x.EncryptMethod.c_str()), allocator); proxy.AddMember("password", rapidjson::StringRef(x.Password.c_str()), allocator); if(!x.Plugin.empty() && !x.PluginOption.empty()) @@ -2243,6 +2241,7 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v proxy.AddMember("tcp_fast_open", buildBooleanValue(tfo), allocator); } nodelist.push_back(x); + remarks_list.emplace_back(x.Remark); outbounds.PushBack(proxy, allocator); } for (const ProxyGroupConfig &x: extra_proxy_group) @@ -2293,6 +2292,21 @@ void proxyToSingBox(std::vector &nodes, rapidjson::Document &json, std::v } outbounds.PushBack(group, allocator); } + + if (global.singBoxAddClashModes) + { + auto global_group = rapidjson::Value(rapidjson::kObjectType); + global_group.AddMember("type", "selector", allocator); + global_group.AddMember("tag", "GLOBAL", allocator); + global_group.AddMember("outbounds", rapidjson::Value(rapidjson::kArrayType), allocator); + global_group["outbounds"].PushBack("DIRECT", allocator); + for (auto &x: remarks_list) + { + global_group["outbounds"].PushBack(rapidjson::Value(x.c_str(), allocator), allocator); + } + outbounds.PushBack(global_group, allocator); + } + json | AddMemberOrReplace("outbounds", outbounds, allocator); } diff --git a/src/handler/settings.cpp b/src/handler/settings.cpp index e0be580ae..51f4437b7 100644 --- a/src/handler/settings.cpp +++ b/src/handler/settings.cpp @@ -376,6 +376,7 @@ void readYAMLConf(YAML::Node &node) section["append_sub_userinfo"] >> global.appendUserinfo; section["clash_use_new_field_name"] >> global.clashUseNewField; section["clash_proxies_style"] >> global.clashProxiesStyle; + section["singbox_add_clash_modes"] >> global.singBoxAddClashModes; } if(section["rename_node"].IsSequence()) @@ -634,7 +635,8 @@ void readTOMLConf(toml::value &root) "filter_deprecated_nodes", global.filterDeprecated, "append_sub_userinfo", global.appendUserinfo, "clash_use_new_field_name", global.clashUseNewField, - "clash_proxies_style", global.clashProxiesStyle + "clash_proxies_style", global.clashProxiesStyle, + "singbox_add_clash_modes", global.singBoxAddClashModes ); auto renameconfs = toml::find_or>(section_node_pref, "rename_node", {}); @@ -878,6 +880,7 @@ void readConf() ini.get_bool_if_exist("append_sub_userinfo", global.appendUserinfo); ini.get_bool_if_exist("clash_use_new_field_name", global.clashUseNewField); ini.get_if_exist("clash_proxies_style", global.clashProxiesStyle); + ini.get_bool_if_exist("singbox_add_clash_modes", global.singBoxAddClashModes); if(ini.item_prefix_exist("rename_node")) { ini.get_all("rename_node", tempArray); diff --git a/src/handler/settings.h b/src/handler/settings.h index 8e48ab983..d2d6932aa 100644 --- a/src/handler/settings.h +++ b/src/handler/settings.h @@ -46,7 +46,7 @@ struct Settings bool addEmoji = false, removeEmoji = false, appendType = false, filterDeprecated = true; tribool UDPFlag, TFOFlag, skipCertVerify, TLS13Flag, enableInsert; bool enableSort = false, updateStrict = false; - bool clashUseNewField = false; + bool clashUseNewField = false, singBoxAddClashModes = true; std::string clashProxiesStyle = "flow"; std::string proxyConfig, proxyRuleset, proxySubscription; int updateInterval = 0;