Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions tachyon/files/usr/lib/components/verifier.uc
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,10 @@ function check_sing_box_config_with_binary(binary, config_path, library_dir, tar
common.command_from_args([
"ucode", "-L", helpers.LIB_DIR, helpers.LIB_DIR + "/singbox/generator.uc",
"generate-config", candidate_cfg, "127.0.0.1", "0", "0", ""
]) + " >/dev/null 2>&1";
if (common.command_status(gen_cmd) == 0 && helpers.file_nonempty(candidate_cfg)) {
]) + " >" + common.shell_quote(err_file) + " 2>&1";
let gen_status = common.command_status(gen_cmd);
if (gen_status == 0 && helpers.file_nonempty(candidate_cfg)) {
helpers.remove_file(err_file);
let repaired = false;
for (let attempt = 0; attempt < 4; attempt++) {
let cand_check_cmd = helpers.command_env(env_map) + " " +
Expand Down Expand Up @@ -308,14 +310,20 @@ function check_sing_box_config_with_binary(binary, config_path, library_dir, tar
break;
}
}
let is_gen_failure = (cand_status == -1);
if (reason == "") {
if (cand_status == 247 || cand_status == 137)
reason = "Out of memory (OOM killed, exit status " + cand_status + ")";
else if (is_gen_failure)
reason = "candidate configuration generation failed";
else
reason = "exit status " + cand_status;
}

helpers.updates_log("Pre-flight check failed: binary " + binary + " rejected config for " + target_var + ": " + reason, "error");
if (is_gen_failure)
helpers.updates_log("Pre-flight check failed: unable to generate configuration for " + target_var + ": " + reason, "error");
else
helpers.updates_log("Pre-flight check failed: binary " + binary + " rejected config for " + target_var + ": " + reason, "error");
return { ok: false, reason: reason };
}

Expand Down
5 changes: 5 additions & 0 deletions tachyon/files/usr/lib/singbox/generator_outbounds.uc
Original file line number Diff line number Diff line change
Expand Up @@ -1530,6 +1530,11 @@ function add_connections_outbound(config, section, taken) {
if (length(selector_tags) == 0) {
if (ctx.deferred_sections && ctx.deferred_sections[section_name])
return;
if (getenv("SB_PREFLIGHT_BINARY")) {
ctx.deferred_sections = ctx.deferred_sections || {};
ctx.deferred_sections[section_name] = true;
return;
}
ctx.runtime_generate_unsupported("connection section has no usable outbounds");
}

Expand Down
2 changes: 1 addition & 1 deletion tachyon/files/usr/lib/singbox/generator_routes.uc
Original file line number Diff line number Diff line change
Expand Up @@ -2592,7 +2592,7 @@ function add_service_route_rules(config, sections) {
let settings = object_or_empty(ctx.uci_cursor().get_all(CONFIG_NAME, "settings"));
if (bool_option(settings, "dns_detour_enabled", false)) {
let detour_section = option(settings, "dns_detour_section", "");
if (detour_section != "") {
if (detour_section != "" && !(ctx.deferred_sections && ctx.deferred_sections[detour_section])) {
let detour_target = outbound_tag(detour_section);
let dns_servers = list_option(settings, "dns_server");
if (length(dns_servers) == 0) {
Expand Down
66 changes: 66 additions & 0 deletions tests/sing_box_preflight_uncached_subscriptions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Verifies sing-box pre-flight check resilience when connection sections have
# subscriptions but no local cache exists yet (e.g. fresh installation after
# firmware flash with restored UCI backup).
#
# 1. generator_outbounds auto-defers connection sections with no usable outbounds
# during pre-flight candidate generation (SB_PREFLIGHT_BINARY).
# 2. verifier captures generator stderr on candidate config generation failure
# and accurately distinguishes generator failures from binary rejection.
. "$(dirname "${BASH_SOURCE[0]}")/lib/harness.sh"
set -eo pipefail

if [ -z "$TACHYON_LIB" ]; then
if [ -d "$ROOT_DIR/tachyon/files/usr/lib/components" ]; then
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
else
TACHYON_LIB="/usr/lib/tachyon"
fi
fi

ucode() { command ucode -L "$TACHYON_LIB" "$@"; }

VERIFIER="$TACHYON_LIB/components/verifier.uc"
OUTBOUNDS="$TACHYON_LIB/singbox/generator_outbounds.uc"
ROUTES="$TACHYON_LIB/singbox/generator_routes.uc"
[ -f "$VERIFIER" ] || fail "missing $VERIFIER"
[ -f "$OUTBOUNDS" ] || fail "missing $OUTBOUNDS"
[ -f "$ROUTES" ] || fail "missing $ROUTES"

pass_count=0
ok() { pass_count=$((pass_count + 1)); }

# Check 1: generator_outbounds contains pre-flight auto-defer logic
if ! grep -q 'getenv("SB_PREFLIGHT_BINARY")' "$OUTBOUNDS"; then
fail "generator_outbounds must check SB_PREFLIGHT_BINARY when connection section has no usable outbounds"
fi
ok

# Check 2: verifier captures candidate generation errors into err_file
verifier_body="$(sed -n '/^function check_sing_box_config_with_binary/,/^}/p' "$VERIFIER")"
[ -n "$verifier_body" ] || fail "check_sing_box_config_with_binary not found in $VERIFIER"

if ! grep -q 'common.shell_quote(err_file)' <<<"$verifier_body"; then
fail "verifier candidate generation must redirect output to err_file, not /dev/null"
fi
ok

# Check 3: verifier distinguishes candidate generation failure from binary rejection
if ! grep -q 'is_gen_failure' <<<"$verifier_body"; then
fail "verifier must distinguish candidate generation failure from binary rejection"
fi
ok

if ! grep -q 'unable to generate configuration' <<<"$verifier_body"; then
fail "verifier must log generation failure message when generator fails"
fi
ok

# Check 4: generator_routes guards dns_detour_section when deferred
if ! grep -q 'ctx.deferred_sections\[detour_section\]' "$ROUTES"; then
fail "generator_routes must check if dns_detour_section is deferred before adding detour route rules"
fi
ok

printf 'sing-box preflight uncached subscriptions: %d checks passed\n' "$pass_count"
printf 'PASS: sing_box_preflight_uncached_subscriptions\n'
Loading