fix(singbox): allow routed DNS on bypass sections and hide proxy group outbounds in urltest

This commit is contained in:
Dushnilin
2026-09-17 12:35:14 +03:00
parent 5df1124ee8
commit df61efce80
11 changed files with 137 additions and 24 deletions
+2 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TACHYON\n" "Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-17 08:16+0300\n" "POT-Creation-Date: 2026-09-17 09:35+0300\n"
"PO-Revision-Date: 2026-09-17 08:16+0300\n" "PO-Revision-Date: 2026-09-17 09:35+0300\n"
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n" "Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
+2 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TACHYON\n" "Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-17 11:16+0300\n" "POT-Creation-Date: 2026-09-17 12:35+0300\n"
"PO-Revision-Date: 2026-09-17 11:16+0300\n" "PO-Revision-Date: 2026-09-17 12:35+0300\n"
"Last-Translator: Dushnilin\n" "Last-Translator: Dushnilin\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: ru\n" "Language: ru\n"
+4
View File
@@ -730,6 +730,10 @@ function ends_with(value, suffix) {
return length(value) >= length(suffix) && substr(value, length(value) - length(suffix)) == suffix; return length(value) >= length(suffix) && substr(value, length(value) - length(suffix)) == suffix;
} }
function file_exists(path) {
return fs.stat(as_string(path)) != null;
}
let uci_cursor_state = false; let uci_cursor_state = false;
function words(value) { function words(value) {
+2 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TACHYON\n" "Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-17 11:16+0300\n" "POT-Creation-Date: 2026-09-17 12:35+0300\n"
"PO-Revision-Date: 2026-09-17 11:16+0300\n" "PO-Revision-Date: 2026-09-17 12:35+0300\n"
"Last-Translator: Dushnilin\n" "Last-Translator: Dushnilin\n"
"Language-Team: none\n" "Language-Team: none\n"
"Language: ru\n" "Language: ru\n"
+2 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: TACHYON\n" "Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-09-17 08:16+0300\n" "POT-Creation-Date: 2026-09-17 09:35+0300\n"
"PO-Revision-Date: 2026-09-17 08:16+0300\n" "PO-Revision-Date: 2026-09-17 09:35+0300\n"
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n" "Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n" "Language: \n"
+14 -2
View File
@@ -1864,8 +1864,20 @@ function finish_component_job(path, component, action, exit_code, output_file) {
// reason; fall back to the result file only if stderr is empty. // reason; fall back to the result file only if stderr is empty.
let raw_output = file_last_nonblank_line_value(output_file + ".stderr", "", 240); let raw_output = file_last_nonblank_line_value(output_file + ".stderr", "", 240);
if (raw_output == "") if (raw_output == "")
raw_output = file_last_nonblank_line_value(output_file, "Failed to execute", 240); raw_output = file_last_nonblank_line_value(output_file, "", 240);
ok = write_state_file(path, component_fallback_job_state(component, action, raw_output, exit_code, updated_at)); let log_file = substr(output_file, 0, length(output_file) - 4) + ".log";
if (raw_output == "" && file_exists_value(log_file))
raw_output = file_last_nonblank_line_value(log_file, "", 240);
let is_success = arg_number(exit_code) == 0;
if (raw_output == "")
raw_output = is_success ? sprintf("%s %s completed successfully", component, action) : "Failed to execute";
let state = component_fallback_job_state(component, action, raw_output, exit_code, updated_at);
if (is_success) {
state.success = true;
state.status = "latest";
state.changed = 1;
}
ok = write_state_file(path, state);
} }
remove_file(output_file); remove_file(output_file);
+2 -2
View File
@@ -1621,8 +1621,8 @@ function validate_rule(section, sections, context) {
validate_combined_domain_text_value(option(section, "domain_suffix_text", ""), name); validate_combined_domain_text_value(option(section, "domain_suffix_text", ""), name);
if (connections.routed_dns_enabled(section)) { if (connections.routed_dns_enabled(section)) {
if (action == "dns" || action == "hosts" || action == "bypass" || action == "block") if (action == "dns" || action == "hosts" || action == "block")
fail_validation("Rule '" + name + "' has routed DNS enabled but action '" + action + "' does not support routed DNS. Use a proxy action (connection, zapret, etc.). Aborted."); fail_validation("Rule '" + name + "' has routed DNS enabled but action '" + action + "' does not support routed DNS. Aborted.");
let routed_type = connections.routed_dns_type(section); let routed_type = connections.routed_dns_type(section);
if (!contains(["udp", "dot", "doh", "doq"], routed_type)) if (!contains(["udp", "dot", "doh", "doq"], routed_type))
fail_validation("Rule '" + name + "' has invalid routed DNS type '" + routed_type + "'. Use udp, dot, doh, or doq. Aborted."); fail_validation("Rule '" + name + "' has invalid routed DNS type '" + routed_type + "'. Use udp, dot, doh, or doq. Aborted.");
@@ -1197,6 +1197,8 @@ function add_subscription_source_with_state(config, section, source_index, sourc
if (hidden_flags[i] !== true) { if (hidden_flags[i] !== true) {
push(selector_tags, outbound.tag); push(selector_tags, outbound.tag);
runtime_subscription.remember_urltest_group(state, outbound.tag, display_names[i], outbound); runtime_subscription.remember_urltest_group(state, outbound.tag, display_names[i], outbound);
} else if (state && state.hiddenOutboundTags) {
state.hiddenOutboundTags[outbound.tag] = true;
} }
} }
return added; return added;
@@ -410,12 +410,24 @@ function filter_candidate_outbounds(filter_mode, urltest_candidate_tags, names,
include_proxy_parameters, include_protocols, include_transports, include_securities, include_proxy_parameters, include_protocols, include_transports, include_securities,
exclude_names, exclude_regex, exclude_countries, exclude_names, exclude_regex, exclude_countries,
exclude_proxy_parameters, exclude_protocols, exclude_transports, exclude_securities, exclude_proxy_parameters, exclude_protocols, exclude_transports, exclude_securities,
include_additional_matches, exclude_additional_matches) { include_additional_matches, exclude_additional_matches,
hidden_tags) {
let all_outbounds = urltest_all_candidate_outbounds(urltest_candidate_tags); let all_outbounds = urltest_all_candidate_outbounds(urltest_candidate_tags);
let visible_outbounds = all_outbounds;
if (hidden_tags && length(keys(hidden_tags)) > 0) {
let filtered = [];
for (let tag in all_outbounds) {
if (!hidden_tags[tag])
push(filtered, tag);
}
if (length(filtered) > 0)
visible_outbounds = filtered;
}
if (filter_mode == "" || filter_mode == "disabled") if (filter_mode == "" || filter_mode == "disabled")
return all_outbounds; return visible_outbounds;
if (!supported_urltest_filter_mode(filter_mode)) if (!supported_urltest_filter_mode(filter_mode))
return all_outbounds; return visible_outbounds;
let include_outbounds = urltest_matching_candidate_outbounds( let include_outbounds = urltest_matching_candidate_outbounds(
urltest_candidate_tags, urltest_candidate_tags,
@@ -451,10 +463,10 @@ function filter_candidate_outbounds(filter_mode, urltest_candidate_tags, names,
if (filter_mode == "include") if (filter_mode == "include")
return include_outbounds; return include_outbounds;
if (filter_mode == "exclude") if (filter_mode == "exclude")
return urltest_exclude_outbounds(all_outbounds, exclude_outbounds); return urltest_exclude_outbounds(visible_outbounds, exclude_outbounds);
if (filter_mode == "mixed") if (filter_mode == "mixed")
return urltest_exclude_outbounds(include_outbounds, exclude_outbounds); return urltest_exclude_outbounds(include_outbounds, exclude_outbounds);
return all_outbounds; return visible_outbounds;
} }
function urltest_filtered_outbounds(section, urltest_id, urltest_candidate_tags, state) { function urltest_filtered_outbounds(section, urltest_id, urltest_candidate_tags, state) {
@@ -477,7 +489,10 @@ function urltest_filtered_outbounds(section, urltest_id, urltest_candidate_tags,
connections.urltest_exclude_proxy_parameters(section, urltest_id), connections.urltest_exclude_proxy_parameters(section, urltest_id),
connections.urltest_exclude_protocols(section, urltest_id), connections.urltest_exclude_protocols(section, urltest_id),
connections.urltest_exclude_transports(section, urltest_id), connections.urltest_exclude_transports(section, urltest_id),
connections.urltest_exclude_securities(section, urltest_id) connections.urltest_exclude_securities(section, urltest_id),
null,
null,
state ? state.hiddenOutboundTags : null
); );
} }
@@ -520,7 +535,10 @@ function priority_level_filtered_outbounds(group_id, level_id, urltest_candidate
connections.priority_level_exclude_proxy_parameters(group_id, level_id), connections.priority_level_exclude_proxy_parameters(group_id, level_id),
connections.priority_level_exclude_protocols(group_id, level_id), connections.priority_level_exclude_protocols(group_id, level_id),
connections.priority_level_exclude_transports(group_id, level_id), connections.priority_level_exclude_transports(group_id, level_id),
connections.priority_level_exclude_securities(group_id, level_id) connections.priority_level_exclude_securities(group_id, level_id),
null,
null,
state ? state.hiddenOutboundTags : null
); );
} }
@@ -1339,10 +1357,10 @@ function push_dns_matcher_rule(config, rule) {
} }
function section_dns_server(section) { function section_dns_server(section) {
if (option(section, "action", "") == "bypass")
return runtime_constants.DNS_SERVER_TAG;
if (connections.routed_dns_enabled(section)) if (connections.routed_dns_enabled(section))
return runtime_constants.tag(section[".name"], "routed-dns-server"); return runtime_constants.tag(section[".name"], "routed-dns-server");
if (option(section, "action", "") == "bypass")
return runtime_constants.DNS_SERVER_TAG;
return runtime_constants.FAKEIP_DNS_SERVER_TAG; return runtime_constants.FAKEIP_DNS_SERVER_TAG;
} }
@@ -1406,10 +1424,11 @@ function add_routed_dns_server_for_section(config, section) {
if (!connections.routed_dns_enabled(section)) if (!connections.routed_dns_enabled(section))
return; return;
let section_name = section[".name"]; let section_name = section[".name"];
let action = option(section, "action", "");
let servers = connections.routed_dns_servers(section); let servers = connections.routed_dns_servers(section);
let dns_type = connections.routed_dns_type(section); let dns_type = connections.routed_dns_type(section);
let tag_name = routed_dns_server_tag(section_name); let tag_name = routed_dns_server_tag(section_name);
let detour = outbound_tag(section_name); let detour = action == "bypass" ? null : outbound_tag(section_name);
for (let i = 0; i < length(servers); i++) { for (let i = 0; i < length(servers); i++) {
let s_val = servers[i]; let s_val = servers[i];
@@ -2128,7 +2147,7 @@ function add_outbound_for_section(config, section, taken, sections) {
ctx.runtime_generate_unsupported("unsupported action " + action); ctx.runtime_generate_unsupported("unsupported action " + action);
} }
if (action != "dns" && action != "hosts" && action != "bypass" && action != "block") if (action != "dns" && action != "hosts" && action != "block")
add_routed_dns_server_for_section(config, section); add_routed_dns_server_for_section(config, section);
} }
@@ -347,7 +347,8 @@ function new_section_state(section_name) {
urltestCandidateTags: [], urltestCandidateTags: [],
urltestGroups: {}, urltestGroups: {},
priorityGroups: {}, priorityGroups: {},
subscriptionMetadata: [] subscriptionMetadata: [],
hiddenOutboundTags: {}
}; };
} }
+75
View File
@@ -411,4 +411,79 @@ if [ -n "$SB_BIN" ]; then
fi fi
fi fi
# 9. Test bypass section with routed DNS
cat >"$WORK_DIR/bypass_routed_dns_fixture.json" <<'EOF'
{
"settings": {
".name": "settings",
".type": "settings",
"dns_server": [ "77.88.8.8" ],
"bootstrap_dns_server": [ "77.88.8.8" ]
},
"section": [
{
".name": "ByPass",
".type": "section",
"enabled": "1",
"action": "bypass",
"community_lists": [ "russia_outside" ],
"routed_dns_enabled": "1",
"routed_dns_type": "doh",
"routed_dns_server": [ "https://dns.comss.one/dns-query" ]
}
]
}
EOF
output_bypass="$WORK_DIR/output_bypass.json"
mkdir -p "$output_bypass.section-cache" "$output_bypass.rulesets"
SB_VERSION_STATE_FILE="$WORK_DIR/sb_v14" \
ucode -L "$TACHYON_LIB" "$GENERATOR_UC" generate-config-fixture \
"$WORK_DIR/bypass_routed_dns_fixture.json" "$output_bypass" "127.0.0.1" "0" "1"
ucode -e '
let fs = require("fs");
let cfg = json(fs.readfile(ARGV[0]));
let found_server = false;
for (let s in cfg.dns.servers || []) {
if (s.tag == "ByPass-routed-dns-server") {
found_server = true;
if (s.type != "https" || s.server != "dns.comss.one" || s.detour) {
warn("ByPass-routed-dns-server properties mismatch: " + json(s) + "\n");
exit(1);
}
}
}
if (!found_server) {
warn("ByPass-routed-dns-server not found in dns.servers\n");
exit(1);
}
let found_dns_rule = false;
for (let r in cfg.dns.rules || []) {
if (r.server == "ByPass-routed-dns-server")
found_dns_rule = true;
}
if (!found_dns_rule) {
warn("DNS rule targeting ByPass-routed-dns-server not found\n");
exit(1);
}
let found_route_rule = false;
for (let r in cfg.route.rules || []) {
if (r.outbound == "bypass-out" && r.rule_set == "ByPass-russia_outside-community-ruleset")
found_route_rule = true;
}
if (!found_route_rule) {
warn("Route rule for ByPass community ruleset not found\n");
exit(1);
}
' "$output_bypass" || fail "bypass with routed DNS verification failed"
if [ -n "$SB_BIN" ]; then
INSTALLED_SB_VER="$("$SB_BIN" version 2>/dev/null | head -n1 | grep -oE '[0-9]+\.[0-9]+' | head -n1 || echo '0.0')"
if [ "$(printf '%s\n1.14\n' "$INSTALLED_SB_VER" | sort -V | head -n1)" = "1.14" ]; then
"$SB_BIN" check -c "$output_bypass" || fail "sing-box 1.14 check failed on bypass with routed DNS config!"
fi
fi
echo "sing-box 1.14 DNS rules tests passed" echo "sing-box 1.14 DNS rules tests passed"