diff --git a/tachyon/files/usr/lib/singbox/generator_routes.uc b/tachyon/files/usr/lib/singbox/generator_routes.uc index d2c2627f..c626c66a 100644 --- a/tachyon/files/usr/lib/singbox/generator_routes.uc +++ b/tachyon/files/usr/lib/singbox/generator_routes.uc @@ -652,6 +652,48 @@ function is_valid_detour(config, tag) { return false; } +function section_excluded_candidate_tags(section, candidate_tags, state) { + let mode = connections.dashboard_filter_mode(section); + if (mode != "exclude" && mode != "mixed") + return []; + + let exclude_names = connections.dashboard_exclude_outbounds(section); + let exclude_regex = connections.dashboard_exclude_regex(section); + let exclude_countries = connections.dashboard_exclude_countries(section); + let exclude_proxy_parameters = connections.dashboard_exclude_proxy_parameters(section); + let exclude_protocols = connections.dashboard_exclude_protocols(section); + let exclude_transports = connections.dashboard_exclude_transports(section); + let exclude_securities = connections.dashboard_exclude_securities(section); + + let has_exclude_criteria = length(exclude_names) > 0 || + length(exclude_regex) > 0 || + length(exclude_countries) > 0 || + (exclude_proxy_parameters && ( + length(exclude_protocols) > 0 || + length(exclude_transports) > 0 || + length(exclude_securities) > 0 + )); + + if (!has_exclude_criteria) + return []; + + return urltest_matching_candidate_outbounds( + candidate_tags, + object_or_empty(object_or_empty(state.outboundMetadata).names), + dashboard_country_metadata(section, state), + exclude_names, + exclude_regex, + exclude_countries, + object_or_empty(state.outboundMetadata), + exclude_proxy_parameters, + "or", + exclude_protocols, + exclude_transports, + exclude_securities, + [] + ); +} + function add_proxy_selector(config, section, selector_tags, urltest_candidate_tags, state) { let section_name = section[".name"]; let selector_tag = outbound_tag(section_name); @@ -661,8 +703,13 @@ function add_proxy_selector(config, section, selector_tags, urltest_candidate_ta let priority_tags = []; let group_outbounds = {}; + let section_excluded = section_excluded_candidate_tags(section, urltest_candidate_tags, state); + let group_candidate_tags = length(section_excluded) > 0 + ? urltest_exclude_outbounds(urltest_candidate_tags, section_excluded) + : urltest_candidate_tags; + for (let urltest_id in connections.urltests(section)) { - let urltest = add_urltest_outbound(config, section, urltest_id, urltest_candidate_tags, state); + let urltest = add_urltest_outbound(config, section, urltest_id, group_candidate_tags, state); remember_dashboard_group_outbounds( group_outbounds, connections.urltest_display_name(section, urltest_id), @@ -675,7 +722,7 @@ function add_proxy_selector(config, section, selector_tags, urltest_candidate_ta } for (let group_id in connections.priority_groups(section)) { - let priority = add_priority_group_outbound(config, section, group_id, urltest_candidate_tags, state); + let priority = add_priority_group_outbound(config, section, group_id, group_candidate_tags, state); remember_dashboard_group_outbounds( group_outbounds, connections.priority_group_display_name(section, group_id), diff --git a/tests/dashboard_server_filter.sh b/tests/dashboard_server_filter.sh index 0690c43a..f00f85e4 100644 --- a/tests/dashboard_server_filter.sh +++ b/tests/dashboard_server_filter.sh @@ -186,4 +186,96 @@ if (mixed.default != "proxy-urltest-ut_main-out") "$WORK_DIR/exclude-config.json" "$WORK_DIR/mixed-config.json" || fail "dashboard server filter regression" +# ─── Issue #89: Section server exclusion cascades to URLTest & Priority groups ─ +cat >"$WORK_DIR/issue89.json" <<'JSON' +{ + "settings": { + ".name": "settings", + ".type": "settings", + "dns_server": [ "77.88.8.8" ], + "bootstrap_dns_server": [ "77.88.8.8" ] + }, + "section": [ + { + ".name": "proxy", + ".type": "section", + "enabled": "1", + "action": "connection", + "outbound_jsons": [ + "{\"type\":\"vless\",\"tag\":\"🇳🇱 Amsterdam\",\"server\":\"nl.example\",\"server_port\":443,\"uuid\":\"00000000-0000-4000-8000-000000000001\"}", + "{\"type\":\"vless\",\"tag\":\"🇷🇺 Moscow\",\"server\":\"ru.example\",\"server_port\":443,\"uuid\":\"00000000-0000-4000-8000-000000000002\"}", + "{\"type\":\"vless\",\"tag\":\"🇩🇪 Berlin\",\"server\":\"de.example\",\"server_port\":443,\"uuid\":\"00000000-0000-4000-8000-000000000003\"}" + ], + "dashboard_filter_mode": "exclude", + "dashboard_detect_server_country": "flag_emoji", + "dashboard_exclude_countries": [ "RU" ] + } + ], + "urltest": [ + { + ".name": "ut_auto", + ".type": "urltest", + "section": "proxy", + "name": "Auto Main", + "filter_mode": "disabled" + } + ], + "priority_group": [ + { + ".name": "pg_auto", + ".type": "priority_group", + "section": "proxy", + "name": "Priority Main" + } + ], + "priority_level": [ + { + ".name": "pl_auto", + ".type": "priority_level", + "group": "pg_auto", + "name": "Level 1", + "order": "0", + "filter_mode": "disabled" + } + ] +} +JSON + +validate_fixture "$WORK_DIR/issue89.json" >/dev/null || fail "issue89 fixture validation" +generate_config "$WORK_DIR/issue89.json" "$WORK_DIR/issue89-config.json" + +ucode -e ' +let fs = require("fs"); +function fail(message) { die(message + "\n"); } +function outbound_by_tag(config, tag) { + for (let outbound in config.outbounds || []) + if (outbound && outbound.tag == tag) + return outbound; + return null; +} +function assert_array(value, expected, label) { + value = value || []; + if (length(value) != length(expected)) + fail(label + " length mismatch: " + sprintf("%J", value)); + for (let i = 0; i < length(expected); i++) + if (value[i] != expected[i]) + fail(label + " mismatch: " + sprintf("%J", value)); +} +let cfg = json(fs.readfile(ARGV[0])); +let selector = outbound_by_tag(cfg, "proxy-out"); +let urltest = outbound_by_tag(cfg, "proxy-urltest-ut_auto-out"); +let priority = outbound_by_tag(cfg, "proxy-priority-pg_auto-out"); + +if (!selector) fail("missing proxy-out selector"); +if (!urltest) fail("missing proxy-urltest-ut_auto-out outbound"); +if (!priority) fail("missing proxy-priority-pg_auto-out outbound"); + +assert_array(urltest.outbounds, [ "🇳🇱 Amsterdam", "🇩🇪 Berlin" ], "URLTest outbounds must not include section-excluded RU server"); +assert_array(priority.outbounds, [ "🇳🇱 Amsterdam", "🇩🇪 Berlin" ], "Priority outbounds must not include section-excluded RU server"); +assert_array(selector.outbounds, [ "🇳🇱 Amsterdam", "🇩🇪 Berlin", "proxy-urltest-ut_auto-out", "proxy-priority-pg_auto-out" ], "Selector outbounds must not include section-excluded RU server"); + +if (selector.default != "proxy-urltest-ut_auto-out") + fail("selector default must be the urltest group"); +' "$WORK_DIR/issue89-config.json" || fail "issue89 cascade exclusion regression" + printf 'dashboard server filter checks passed\n'