fix(singbox): cascade section-level server exclusions to URLTest and Priority groups (#89)

This commit is contained in:
Dushnilin
2026-09-09 21:56:00 +03:00
parent 7fe6a84f80
commit 3ee5cf2598
2 changed files with 141 additions and 2 deletions
@@ -652,6 +652,48 @@ function is_valid_detour(config, tag) {
return false; 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) { function add_proxy_selector(config, section, selector_tags, urltest_candidate_tags, state) {
let section_name = section[".name"]; let section_name = section[".name"];
let selector_tag = outbound_tag(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 priority_tags = [];
let group_outbounds = {}; 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)) { 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( remember_dashboard_group_outbounds(
group_outbounds, group_outbounds,
connections.urltest_display_name(section, urltest_id), 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)) { 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( remember_dashboard_group_outbounds(
group_outbounds, group_outbounds,
connections.priority_group_display_name(section, group_id), connections.priority_group_display_name(section, group_id),
+92
View File
@@ -186,4 +186,96 @@ if (mixed.default != "proxy-urltest-ut_main-out")
"$WORK_DIR/exclude-config.json" "$WORK_DIR/mixed-config.json" || "$WORK_DIR/exclude-config.json" "$WORK_DIR/mixed-config.json" ||
fail "dashboard server filter regression" 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' printf 'dashboard server filter checks passed\n'