fix(singbox): allow routed DNS on bypass sections and hide proxy group outbounds in urltest
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user