From 287a8f8e0c8e5157c87cb82c1d0d217cfee7e3bb Mon Sep 17 00:00:00 2001 From: Dushnilin Date: Thu, 17 Sep 2026 10:46:04 +0300 Subject: [PATCH] fix(singbox): resolve 1.14.1 DNS deprecation, store_rdrc, ruleset download_detour, and provider routing isolation --- fe-app-tachyon/locales/tachyon.pot | 4 +- fe-app-tachyon/locales/tachyon.ru.po | 4 +- luci-app-tachyon/po/ru/tachyon.po | 4 +- luci-app-tachyon/po/templates/tachyon.pot | 4 +- tachyon/files/usr/lib/config/connections.uc | 93 +++++++++++- tachyon/files/usr/lib/config/validator.uc | 13 ++ .../files/usr/lib/providers/fptn/runtime.uc | 12 +- .../usr/lib/providers/nfqueue/runtime.uc | 7 +- tachyon/files/usr/lib/routing/rulesets.uc | 36 +++++ tachyon/files/usr/lib/singbox/generator.uc | 133 +++++++++++------- .../usr/lib/singbox/generator_outbounds.uc | 14 ++ .../files/usr/lib/singbox/generator_routes.uc | 69 +++++++-- tachyon/files/usr/lib/singbox/rulesets.uc | 25 +++- 13 files changed, 335 insertions(+), 83 deletions(-) diff --git a/fe-app-tachyon/locales/tachyon.pot b/fe-app-tachyon/locales/tachyon.pot index 1ef7ec8e..5c2eadc0 100644 --- a/fe-app-tachyon/locales/tachyon.pot +++ b/fe-app-tachyon/locales/tachyon.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: TACHYON\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-17 05:45+0300\n" -"PO-Revision-Date: 2026-09-17 05:45+0300\n" +"POT-Creation-Date: 2026-09-17 07:46+0300\n" +"PO-Revision-Date: 2026-09-17 07:46+0300\n" "Last-Translator: Dushnilin \n" "Language-Team: LANGUAGE \n" "Language: \n" diff --git a/fe-app-tachyon/locales/tachyon.ru.po b/fe-app-tachyon/locales/tachyon.ru.po index 13648a40..178cb5e9 100644 --- a/fe-app-tachyon/locales/tachyon.ru.po +++ b/fe-app-tachyon/locales/tachyon.ru.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: TACHYON\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-17 08:45+0300\n" -"PO-Revision-Date: 2026-09-17 08:45+0300\n" +"POT-Creation-Date: 2026-09-17 10:46+0300\n" +"PO-Revision-Date: 2026-09-17 10:46+0300\n" "Last-Translator: Dushnilin\n" "Language-Team: none\n" "Language: ru\n" diff --git a/luci-app-tachyon/po/ru/tachyon.po b/luci-app-tachyon/po/ru/tachyon.po index 13648a40..178cb5e9 100644 --- a/luci-app-tachyon/po/ru/tachyon.po +++ b/luci-app-tachyon/po/ru/tachyon.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: TACHYON\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-17 08:45+0300\n" -"PO-Revision-Date: 2026-09-17 08:45+0300\n" +"POT-Creation-Date: 2026-09-17 10:46+0300\n" +"PO-Revision-Date: 2026-09-17 10:46+0300\n" "Last-Translator: Dushnilin\n" "Language-Team: none\n" "Language: ru\n" diff --git a/luci-app-tachyon/po/templates/tachyon.pot b/luci-app-tachyon/po/templates/tachyon.pot index 1ef7ec8e..5c2eadc0 100644 --- a/luci-app-tachyon/po/templates/tachyon.pot +++ b/luci-app-tachyon/po/templates/tachyon.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: TACHYON\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-17 05:45+0300\n" -"PO-Revision-Date: 2026-09-17 05:45+0300\n" +"POT-Creation-Date: 2026-09-17 07:46+0300\n" +"PO-Revision-Date: 2026-09-17 07:46+0300\n" "Last-Translator: Dushnilin \n" "Language-Team: LANGUAGE \n" "Language: \n" diff --git a/tachyon/files/usr/lib/config/connections.uc b/tachyon/files/usr/lib/config/connections.uc index a4a556d9..e3d0fea1 100644 --- a/tachyon/files/usr/lib/config/connections.uc +++ b/tachyon/files/usr/lib/config/connections.uc @@ -751,6 +751,93 @@ function rule_sets_with_subnets_value(section) { return list_option_value_from_array(rule_sets_with_subnets(section)); } +function section_has_routing_matchers(section) { + if (section == null || type(section) != "object") + return false; + + if (has_dns_matchers(section)) + return true; + + if (length(list_option(section, "domain")) > 0 || option(section, "domain", "") != "" || option(section, "domain_text", "") != "" || + length(list_option(section, "domain_suffix")) > 0 || option(section, "domain_suffix", "") != "" || option(section, "domain_suffix_text", "") != "" || + length(list_option(section, "domain_keyword")) > 0 || option(section, "domain_keyword", "") != "" || option(section, "domain_keyword_text", "") != "" || + length(list_option(section, "domain_regex")) > 0 || option(section, "domain_regex", "") != "" || option(section, "domain_regex_text", "") != "" || + length(list_option(section, "user_domains")) > 0 || option(section, "user_domains", "") != "" || option(section, "user_domains_text", "") != "") + return true; + + if (rule_condition_csv(section, "ip_cidr", "subnets") != "" || + rule_condition_csv(section, "source_ip_cidr", "subnets") != "" || + length(list_option(section, "fully_routed_ips")) > 0 || + option(section, "fully_routed_ips", "") != "" || + length(list_option(section, "ip_cidr")) > 0 || + option(section, "ip_cidr", "") != "" || + option(section, "ip_cidr_text", "") != "" || + length(list_option(section, "source_ip_cidr")) > 0 || + option(section, "source_ip_cidr", "") != "" || + option(section, "source_ip_cidr_text", "") != "") + return true; + + if (rule_condition_csv(section, "ports", "ports") != "" || + length(list_option(section, "ports")) > 0 || + option(section, "ports", "") != "" || + option(section, "ports_text", "") != "" || + option(section, "port", "") != "" || + option(section, "port_range", "") != "") + return true; + + if (length(list_option(section, "protocol")) > 0 || + option(section, "protocol", "") != "") + return true; + + if (length(dscp_list(section)) > 0) + return true; + + if (length(geoip_country_list(section)) > 0) + return true; + + return false; +} + +function section_is_active_provider(section, action) { + if (section == null || type(section) != "object") + return false; + let name = section_name(section); + if (name == "") + return false; + if (!bool_option(section, "enabled", true)) + return false; + if (action != null && action != "" && option(section, "action", "") != action) + return false; + + return section_has_routing_matchers(section) || bool_option(section, "match_all", false); +} + +function active_provider_sections(action, sections) { + let result = []; + let list = sections; + if (list == null) + list = uci_core.section_objects(CONFIG_NAME, "section"); + + for (let section in list) { + if (section_is_active_provider(section, action)) + push(result, section); + } + return result; +} + +function active_provider_section_index(action, sections, target_section) { + let target_name = type(target_section) == "object" ? section_name(target_section) : as_string(target_section); + if (target_name == "") + return 0; + + let active = active_provider_sections(action, sections); + for (let i = 0; i < length(active); i++) { + if (section_name(active[i]) == target_name) + return i + 1; + } + return 0; +} + function has_connection_sources(section) { return length(connection_urls(section)) > 0 || length(subscription_urls(section)) > 0 || @@ -1653,5 +1740,9 @@ return { fptn_sections, is_wdtt_action, is_olcrtc_action, - is_fptn_action + is_fptn_action, + section_has_routing_matchers, + section_is_active_provider, + active_provider_sections, + active_provider_section_index }; diff --git a/tachyon/files/usr/lib/config/validator.uc b/tachyon/files/usr/lib/config/validator.uc index e8612582..b9fc0c5a 100644 --- a/tachyon/files/usr/lib/config/validator.uc +++ b/tachyon/files/usr/lib/config/validator.uc @@ -1537,6 +1537,19 @@ function validate_rule(section, sections, context) { fail_validation("FPTN rule '" + name + "': " + vres.error + ". Aborted."); } + let provider_labels = { + zapret: "Zapret", + zapret2: "Zapret2", + byedpi: "ByeDPI", + wdtt: "WDTT", + olcrtc: "OlcRTC", + fptn: "FPTN" + }; + if (provider_labels[action] != null) { + if (!connections.section_has_routing_matchers(section) && !bool_option(section, "match_all", false)) + fail_validation(provider_labels[action] + " rule '" + name + "' has no routing conditions. Add domains, community lists, IP/subnet/port/protocol matchers or enable Match All explicitly. Aborted."); + } + if (connections.is_connections_action(action)) { validate_dashboard_filter(section); diff --git a/tachyon/files/usr/lib/providers/fptn/runtime.uc b/tachyon/files/usr/lib/providers/fptn/runtime.uc index 6439fe12..629e0418 100644 --- a/tachyon/files/usr/lib/providers/fptn/runtime.uc +++ b/tachyon/files/usr/lib/providers/fptn/runtime.uc @@ -36,11 +36,13 @@ function provider_available() { } function enabled_sections() { + let sections = uci_core.section_objects(CONFIG_NAME, "section"); + if (connections && connections.active_provider_sections) + return connections.active_provider_sections("fptn", sections); if (connections && connections.fptn_sections) return connections.fptn_sections(); let result = []; - let sections = uci_core.section_objects(CONFIG_NAME, "section"); for (let s in sections) { if (bool_option(s, "enabled", true) && option(s, "action", "") == "fptn") push(result, s); @@ -94,6 +96,7 @@ function remove_kernel_routing() { } command_status("ip route flush table " + cfg.route_table + " 2>/dev/null; true"); + command_status("ip -4 route del default dev " + shell_quote(cfg.tun_interface) + " table main 2>/dev/null; true"); if (command_status("ip link show " + shell_quote(cfg.tun_interface) + " >/dev/null 2>&1") == 0) command_status("ip link set dev " + shell_quote(cfg.tun_interface) + " down 2>/dev/null; true"); } @@ -116,6 +119,13 @@ function install_kernel_routing() { return false; } + // Ensure table main default route was not hijacked by fptn-client-cli + let main_default_dev = trim(command_output("ip -4 route show table main default 2>/dev/null | awk '{print $5; exit}'")); + if (main_default_dev == cfg.tun_interface) { + log_message("FPTN client hijacked table main default route; restoring WAN route", "warn"); + command_status("ip -4 route del default dev " + shell_quote(cfg.tun_interface) + " table main 2>/dev/null; true"); + } + return true; } diff --git a/tachyon/files/usr/lib/providers/nfqueue/runtime.uc b/tachyon/files/usr/lib/providers/nfqueue/runtime.uc index fbbca0c0..34dc72b2 100644 --- a/tachyon/files/usr/lib/providers/nfqueue/runtime.uc +++ b/tachyon/files/usr/lib/providers/nfqueue/runtime.uc @@ -5,6 +5,7 @@ let common = require("core.common"); let constants = require("core.constants"); let uci_core = require("core.uci"); let runtime_constants = require("singbox.constants"); +let connections = require("config.connections"); let as_string = common.as_string; let bool_value = common.bool_value; @@ -77,11 +78,7 @@ function strategy_words(value) { } function enabled_sections(cfg) { - let result = []; - for (let section in uci_sections("section")) - if (bool_option(section, "enabled", true) && option(section, "action", "") == cfg.action) - push(result, section); - return result; + return connections.active_provider_sections(cfg.action, uci_sections("section")); } let parse_number = common.parse_number; diff --git a/tachyon/files/usr/lib/routing/rulesets.uc b/tachyon/files/usr/lib/routing/rulesets.uc index c0c2efd6..054dc7ad 100644 --- a/tachyon/files/usr/lib/routing/rulesets.uc +++ b/tachyon/files/usr/lib/routing/rulesets.uc @@ -464,6 +464,39 @@ function has_domain_matchers(path) { return value_has_domain_matchers(read_json_file(path)); } +function value_has_ip_matchers(value) { + if (type(value) == "array") { + for (let item in value) { + if (value_has_ip_matchers(item)) + return true; + } + return false; + } + + if (type(value) != "object") + return false; + + for (let key, item in value) { + if (key == "ip_cidr" || key == "source_ip_cidr" || key == "ip_is_private") { + if (type(item) == "array" && length(item) > 0) + return true; + if (type(item) == "string" && item != "") + return true; + if (type(item) == "boolean" && item) + return true; + } + + if (value_has_ip_matchers(item)) + return true; + } + + return false; +} + +function has_ip_matchers(path) { + return value_has_ip_matchers(read_json_file(path)); +} + function has_rules(path) { let ruleset = object_or_empty(read_json_file(path)); return length(array_or_empty(ruleset.rules)) > 0; @@ -507,6 +540,7 @@ function module_exports() { extract_ip_cidr, extract_ip_cidr_nft_elements, has_domain_matchers, + has_ip_matchers, has_rules, compile_plain_list, ruleset_tag, @@ -534,6 +568,8 @@ else if (mode == "extract-ip-cidr-nft") extract_ip_cidr_nft_elements(ARGV[1], ARGV[2], ARGV[3], ARGV[4] || "[]", ARGV[5] || "[]"); else if (mode == "has-domain-matchers") exit(has_domain_matchers(ARGV[1]) ? 0 : 1); +else if (mode == "has-ip-matchers") + exit(has_ip_matchers(ARGV[1]) ? 0 : 1); else if (mode == "has-rules") exit(has_rules(ARGV[1]) ? 0 : 1); else { diff --git a/tachyon/files/usr/lib/singbox/generator.uc b/tachyon/files/usr/lib/singbox/generator.uc index 57012db2..102683cf 100644 --- a/tachyon/files/usr/lib/singbox/generator.uc +++ b/tachyon/files/usr/lib/singbox/generator.uc @@ -422,29 +422,76 @@ function tproxy_inbound_matcher() { return [ runtime_constants.TPROXY_INBOUND_TAG, runtime_constants.TPROXY_INBOUND6_TAG ]; } -function is_sb_1_14_plus_detected(sb_version_val) { - if (sb_version_val == null || sb_version_val == "") { - let sb_version_file = getenv("SB_VERSION_STATE_FILE") || "/etc/tachyon/sing-box-version"; - sb_version_val = trim(fs.readfile(sb_version_file) || ""); - if (sb_version_val == "") { - let sb_ui_cache = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_FILE") || "/var/run/tachyon/ui-state/sing-box-version"; - sb_version_val = trim(fs.readfile(sb_ui_cache) || ""); - } - if (sb_version_val == "") { - try { - let pipe = fs.popen("sing-box version 2>/dev/null", "r"); - if (pipe) { - let out = pipe.read("all"); - pipe.close(); - let m = match(out, /sing-box version ([^\s]+)/); - if (m) - sb_version_val = m[1]; - } - } catch (e) {} +let cached_sb_version = null; + +function detect_sing_box_version() { + if (cached_sb_version != null) + return cached_sb_version; + + let env_file = getenv("SB_VERSION_STATE_FILE"); + if (env_file != null && env_file != "") { + let ver = trim(fs.readfile(env_file) || ""); + if (ver != "") { + cached_sb_version = ver; + return ver; + } + } + + try { + for (let cmd in [ "sing-box version 2>/dev/null", "/usr/bin/sing-box version 2>/dev/null" ]) { + let pipe = fs.popen(cmd, "r"); + if (pipe) { + let out = pipe.read("all"); + pipe.close(); + let m = match(out, /sing-box version ([^\s]+)/); + if (m) { + cached_sb_version = m[1]; + try { + let cur_disk = trim(fs.readfile("/etc/tachyon/sing-box-version") || ""); + if (cur_disk != cached_sb_version) + fs.writefile("/etc/tachyon/sing-box-version", cached_sb_version + "\n"); + } catch (e) {} + return cached_sb_version; + } + } + } + } catch (e) {} + + let sb_version_file = "/etc/tachyon/sing-box-version"; + let sb_version_val = trim(fs.readfile(sb_version_file) || ""); + if (sb_version_val == "") { + let sb_ui_cache = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_FILE") || "/var/run/tachyon/ui-state/sing-box-version"; + sb_version_val = trim(fs.readfile(sb_ui_cache) || ""); + } + cached_sb_version = sb_version_val; + return cached_sb_version; +} + +function is_sb_1_14_plus_detected(sb_version_val) { + if (sb_version_val == null || sb_version_val == "") + sb_version_val = detect_sing_box_version(); + if (sb_version_val != "") { + let m = match(sb_version_val, /^v?([0-9]+)\.([0-9]+)/); + if (m) { + let major = int(m[1]); + let minor = int(m[2]); + return major > 1 || (major == 1 && minor >= 14); + } + } + return false; +} + +function is_sb_1_15_plus_detected(sb_version_val) { + if (sb_version_val == null || sb_version_val == "") + sb_version_val = detect_sing_box_version(); + if (sb_version_val != "") { + let m = match(sb_version_val, /^v?([0-9]+)\.([0-9]+)/); + if (m) { + let major = int(m[1]); + let minor = int(m[2]); + return major > 1 || (major == 1 && minor >= 15); } } - if (sb_version_val != "") - return match(sb_version_val, /^v?1\.(1[4-9]|[2-9][0-9])\./) != null; return false; } @@ -567,39 +614,25 @@ function base_config(settings, service_address, runtime_context) { strategy: option(settings, "dns_strategy", "prefer_ipv4") }; - let sb_version_file = getenv("SB_VERSION_STATE_FILE") || "/etc/tachyon/sing-box-version"; - let sb_version_val = trim(fs.readfile(sb_version_file) || ""); - if (sb_version_val == "") { - let sb_ui_cache = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_FILE") || "/var/run/tachyon/ui-state/sing-box-version"; - sb_version_val = trim(fs.readfile(sb_ui_cache) || ""); - } - if (sb_version_val == "") { - try { - let pipe = fs.popen("sing-box version 2>/dev/null", "r"); - if (pipe) { - let out = pipe.read("all"); - pipe.close(); - let m = match(out, /sing-box version ([^\s]+)/); - if (m) - sb_version_val = m[1]; - } - } catch (e) {} - } - let sb_variant_file = getenv("SB_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant"; - let sb_variant_val = trim(fs.readfile(sb_variant_file) || ""); - let is_extended_variant = sb_variant_val == "extended" || sb_variant_val == "extended-compressed" || index(sb_version_val, "extended") >= 0; - - let use_legacy_rdrc = is_extended_variant || (sb_version_val != "" - ? (match(sb_version_val, /^v?1\.1[0-3]\./) != null) - : false); - + let sb_version_val = detect_sing_box_version(); let is_sb_1_14_plus = is_sb_1_14_plus_detected(sb_version_val); + let is_sb_1_15_plus = is_sb_1_15_plus_detected(sb_version_val); ctx.is_sb_1_14_plus = function() { return is_sb_1_14_plus; }; + ctx.is_sb_1_15_plus = function() { return is_sb_1_15_plus; }; + + let use_legacy_rdrc = !is_sb_1_14_plus; let route_section = runtime_route.config(settings, runtime_context); if (is_sb_1_14_plus) route_section.default_http_client = "ruleset-http"; + if (is_sb_1_14_plus) { + dns_section.optimistic = { + enabled: true, + timeout: "3d" + }; + } + let cache_file_section = { enabled: true, path: cache_path, @@ -610,6 +643,11 @@ function base_config(settings, service_address, runtime_context) { else cache_file_section.store_dns = true; + if (is_sb_1_15_plus) { + cache_file_section.buffer_size = "1MB"; + cache_file_section.flush_interval = "10m"; + } + let base_cfg = { log: { disabled: false, @@ -1547,6 +1585,7 @@ ctx.uci_bin_to_hex = uci_bin_to_hex; ctx.download_detour_tag = download_detour_tag; ctx.atomic_write_json_file = atomic_write_json_file; ctx.is_sb_1_14_plus = is_sb_1_14_plus_detected; +ctx.is_sb_1_15_plus = is_sb_1_15_plus_detected; generator_outbounds.init(ctx); generator_routes.init(ctx); diff --git a/tachyon/files/usr/lib/singbox/generator_outbounds.uc b/tachyon/files/usr/lib/singbox/generator_outbounds.uc index 51ef4238..b7797253 100644 --- a/tachyon/files/usr/lib/singbox/generator_outbounds.uc +++ b/tachyon/files/usr/lib/singbox/generator_outbounds.uc @@ -1772,6 +1772,8 @@ function add_openvpn_endpoint(config, section) { } function enabled_action_index(sections, target_section, action_name) { + if (connections && connections.active_provider_section_index) + return connections.active_provider_section_index(action_name, sections, target_section); let index = 0; for (let section in sections) { if (option(section, "action", "") != action_name) @@ -1784,6 +1786,8 @@ function enabled_action_index(sections, target_section, action_name) { } function add_zapret_outbound(config, section, sections) { + if (connections && !connections.section_is_active_provider(section, "zapret")) + return; let index = enabled_action_index(sections, section, "zapret"); if (index <= 0) ctx.runtime_generate_unsupported("unable to resolve Zapret index for " + section[".name"]); @@ -1795,6 +1799,8 @@ function add_zapret_outbound(config, section, sections) { } function add_zapret2_outbound(config, section, sections) { + if (connections && !connections.section_is_active_provider(section, "zapret2")) + return; let index = enabled_action_index(sections, section, "zapret2"); if (index <= 0) ctx.runtime_generate_unsupported("unable to resolve Zapret2 index for " + section[".name"]); @@ -1806,6 +1812,8 @@ function add_zapret2_outbound(config, section, sections) { } function add_byedpi_outbound(config, section, sections) { + if (connections && !connections.section_is_active_provider(section, "byedpi")) + return; let index = enabled_action_index(sections, section, "byedpi"); if (index <= 0) ctx.runtime_generate_unsupported("unable to resolve ByeDPI index for " + section[".name"]); @@ -1819,6 +1827,8 @@ function add_byedpi_outbound(config, section, sections) { } function add_wdtt_outbound(config, section, sections) { + if (connections && !connections.section_is_active_provider(section, "wdtt")) + return; let index = enabled_action_index(sections, section, "wdtt"); if (index <= 0) ctx.runtime_generate_unsupported("unable to resolve WDTT index for " + section[".name"]); @@ -1832,6 +1842,8 @@ function add_wdtt_outbound(config, section, sections) { } function add_olcrtc_outbound(config, section, sections) { + if (connections && !connections.section_is_active_provider(section, "olcrtc")) + return; let index = enabled_action_index(sections, section, "olcrtc"); if (index <= 0) ctx.runtime_generate_unsupported("unable to resolve OlcRTC index for " + section[".name"]); @@ -1847,6 +1859,8 @@ function add_olcrtc_outbound(config, section, sections) { // FPTN routes traffic through its TUN interface (tun-fptn) via kernel routing. // fwmark 0x00300000 (3145728) → ip rule → routing table 4249 → dev tun-fptn. function add_fptn_outbound(config, section, sections) { + if (connections && !connections.section_is_active_provider(section, "fptn")) + return; push(config.outbounds, { type: "direct", tag: outbound_tag(section[".name"]), diff --git a/tachyon/files/usr/lib/singbox/generator_routes.uc b/tachyon/files/usr/lib/singbox/generator_routes.uc index aedf6427..912ac206 100644 --- a/tachyon/files/usr/lib/singbox/generator_routes.uc +++ b/tachyon/files/usr/lib/singbox/generator_routes.uc @@ -919,6 +919,9 @@ function ensure_custom_ruleset(config, reference) { else if (runtime_rulesets.is_valid_srs_file(etc_srs) || helpers.file_is_usable(etc_srs, 16)) local_path = etc_srs; + if (config.route.rule_set == null) + config.route.rule_set = []; + if (local_path != null) { push(config.route.rule_set, { type: "local", @@ -935,8 +938,7 @@ function ensure_custom_ruleset(config, reference) { url: runtime_rulesets.community_url(reference) }; let detour = ctx.download_detour_tag(ctx.runtime_settings()); - let is_1_14 = ctx.is_sb_1_14_plus && ctx.is_sb_1_14_plus(); - if (!is_1_14 && is_valid_detour(config, detour)) + if (is_valid_detour(config, detour)) rule_set.download_detour = detour; rule_set.update_interval = remote_ruleset_update_interval(); push(config.route.rule_set, rule_set); @@ -946,8 +948,6 @@ function ensure_custom_ruleset(config, reference) { } tag_name = "inline-custom-" + runtime_rulesets.hash12(reference) + "-ruleset"; - if (kind == "unknown") - kind = "domains"; if (ruleset_registered(config, tag_name)) return { tag: tag_name, kind }; @@ -1072,6 +1072,8 @@ function ensure_custom_ruleset(config, reference) { // Skip broken local ruleset files — nonexistent or suspiciously small if (!helpers.file_is_usable(reference, 100)) return null; + if (config.route.rule_set == null) + config.route.rule_set = []; push(config.route.rule_set, { type: "local", tag: tag_name, @@ -1087,10 +1089,11 @@ function ensure_custom_ruleset(config, reference) { url: reference }; let detour = ctx.download_detour_tag(ctx.runtime_settings()); - let is_1_14 = ctx.is_sb_1_14_plus && ctx.is_sb_1_14_plus(); - if (!is_1_14 && is_valid_detour(config, detour)) + if (is_valid_detour(config, detour)) rule_set.download_detour = detour; rule_set.update_interval = remote_ruleset_update_interval(); + if (config.route.rule_set == null) + config.route.rule_set = []; push(config.route.rule_set, rule_set); } else { @@ -1118,6 +1121,9 @@ function ensure_community_ruleset(config, section_name, community) { else if (runtime_rulesets.is_valid_srs_file(etc_srs) || helpers.file_is_usable(etc_srs, 16)) local_path = etc_srs; + if (config.route.rule_set == null) + config.route.rule_set = []; + if (local_path != null) { push(config.route.rule_set, { type: "local", @@ -1141,8 +1147,7 @@ function ensure_community_ruleset(config, section_name, community) { detour = sec_out; } } - let is_1_14 = ctx.is_sb_1_14_plus && ctx.is_sb_1_14_plus(); - if (!is_1_14 && is_valid_detour(config, detour)) + if (is_valid_detour(config, detour)) rule_set.download_detour = detour; push(config.route.rule_set, rule_set); } @@ -1210,7 +1215,7 @@ function rebuild_local_domain_ip_list_ruleset(section_name, references, domains_ } } -function add_domain_ip_list_ruleset(config, section_name, rule_set_tags, dns_rule_set_tags, references, domains_only) { +function add_domain_ip_list_ruleset(config, section_name, rule_set_tags, dns_query_rule_set_tags, dns_response_rule_set_tags, references, domains_only) { if (length(references) == 0) return; @@ -1222,6 +1227,8 @@ function add_domain_ip_list_ruleset(config, section_name, rule_set_tags, dns_rul let tag_name = domain_ip_list_ruleset_tag(section_name); if (!ruleset_registered(config, tag_name)) { + if (config.route.rule_set == null) + config.route.rule_set = []; push(config.route.rule_set, { type: "local", tag: tag_name, @@ -1232,8 +1239,23 @@ function add_domain_ip_list_ruleset(config, section_name, rule_set_tags, dns_rul if (!domains_only) push(rule_set_tags, tag_name); - if (source_rulesets.has_domain_matchers(ruleset_path)) - push(dns_rule_set_tags, tag_name); + + let is_1_14 = ctx.is_sb_1_14_plus && ctx.is_sb_1_14_plus(); + if (source_rulesets.has_domain_matchers(ruleset_path)) { + if (is_1_14 && source_rulesets.has_ip_matchers(ruleset_path)) { + if (dns_response_rule_set_tags != null) + push(dns_response_rule_set_tags, tag_name); + else if (dns_query_rule_set_tags != null) + push(dns_query_rule_set_tags, tag_name); + } else if (dns_query_rule_set_tags != null) { + push(dns_query_rule_set_tags, tag_name); + } + } else if (source_rulesets.has_ip_matchers(ruleset_path)) { + if (is_1_14 && dns_response_rule_set_tags != null) + push(dns_response_rule_set_tags, tag_name); + else if (!is_1_14 && dns_query_rule_set_tags != null) + push(dns_query_rule_set_tags, tag_name); + } } function legacy_condition_values(section, key) { @@ -1441,14 +1463,25 @@ function add_dns_action_rules_for_section(config, section) { } for (let reference in connections.rule_sets(section)) { let ensured = ensure_custom_ruleset(config, as_string(reference)); - if (ensured != null && ensured.kind == "domains") + if (ensured == null) + continue; + if (ensured.kind == "domains") push(query_rule_set_tags, ensured.tag); + else + push(response_rule_set_tags, ensured.tag); + } + for (let reference in connections.rule_sets_with_subnets(section)) { + let ensured = ensure_custom_ruleset(config, as_string(reference)); + if (ensured == null) + continue; + push(response_rule_set_tags, ensured.tag); } add_domain_ip_list_ruleset( config, section_name, [], query_rule_set_tags, + response_rule_set_tags, list_option(section, "domain_ip_lists"), true ); @@ -1833,20 +1866,22 @@ function add_combined_route_for_section(config, section) { push(rule_set_tags, ensured.tag); if (ensured.kind == "domains") push(dns_query_rule_set_tags, ensured.tag); + else + push(dns_response_rule_set_tags, ensured.tag); } for (let reference in connections.rule_sets_with_subnets(section)) { let ensured = ensure_custom_ruleset(config, as_string(reference)); if (ensured == null) continue; push(rule_set_tags, ensured.tag); - if (ensured.kind == "domains") - push(dns_query_rule_set_tags, ensured.tag); + push(dns_response_rule_set_tags, ensured.tag); } add_domain_ip_list_ruleset( config, section_name, rule_set_tags, dns_query_rule_set_tags, + dns_response_rule_set_tags, list_option(section, "domain_ip_lists"), false ); @@ -1945,7 +1980,11 @@ function add_combined_route_for_section(config, section) { } } - if (!has_domain && !has_ruleset && !has_ip_cidr && length(discord_cf_subnets) == 0 && length(country_list) == 0) { + if (bool_option(section, "match_all", false)) { + let all_rule = create_section_route_rule(); + push_section_route_rule(config, all_rule, target.outbound, excluded_cidrs); + } + else if (!has_domain && !has_ruleset && !has_ip_cidr && length(discord_cf_subnets) == 0 && length(country_list) == 0) { let fallback_rule = create_section_route_rule(); let has_any_matcher = fallback_rule.source_ip_cidr != null || fallback_rule.port != null || fallback_rule.port_range != null || diff --git a/tachyon/files/usr/lib/singbox/rulesets.uc b/tachyon/files/usr/lib/singbox/rulesets.uc index f342ff17..b51163e1 100644 --- a/tachyon/files/usr/lib/singbox/rulesets.uc +++ b/tachyon/files/usr/lib/singbox/rulesets.uc @@ -180,13 +180,26 @@ function file_extension(value) { function kind_from_reference_hint(reference) { reference = lc(as_string(reference)); - if (index(reference, "geosite") >= 0 || index(reference, "domain") >= 0 || - index(reference, "domains") >= 0 || index(reference, "adguard") >= 0 || - index(reference, "filter") >= 0) - return "domains"; - if (index(reference, "geoip") >= 0 || index(reference, "subnet") >= 0 || - index(reference, "subnets") >= 0 || index(reference, "cidr") >= 0) + let has_ip_hint = index(reference, "geoip") >= 0 || + index(reference, "subnet") >= 0 || + index(reference, "subnets") >= 0 || + index(reference, "cidr") >= 0 || + match(reference, /(^|[-_.\/])ips?([-_.\/]|$)/) != null; + + let has_domain_hint = index(reference, "geosite") >= 0 || + index(reference, "domain") >= 0 || + index(reference, "domains") >= 0 || + index(reference, "adguard") >= 0 || + index(reference, "adblock") >= 0 || + index(reference, "host") >= 0 || + index(reference, "hosts") >= 0; + + if (has_ip_hint && has_domain_hint) + return "mixed"; + if (has_ip_hint) return "subnets"; + if (has_domain_hint) + return "domains"; return "unknown"; }