fix(providers): resolve zapret2 startup crash, nft desync EPERM, and mieru ruleset crash loop

- fix(zapret2): add safe_str fallback and avoid default --filter-tcp=443 injection on UDP rules in prepare_strategy_args()
- fix(providers): make individual rule failures non-fatal in start_runtime() so one bad rule does not rollback all running rules
- fix(binaries): dynamic candidate resolution for nfqws and nfqws2 across standard OpenWrt and custom paths (/opt/zapret2/nfq2, /opt/zapret2/nfq, /opt/zapret2, /usr/bin)
- fix(nft): add raw_output notrack chain and mangle_output/forward counter return rules for desync marks (0x40000000, 0x20000000) and musl/nobody skuid (2147483647, 65534) to prevent EPERM rawsend failure on reload
- fix(reload): ensure nft_rebuild is triggered on zapret and zapret2 runtime updates
- fix(singbox): reject mieru outbounds from rule_set download_detour and http_clients dial_detour to prevent fatal startup crash loop
- fix(singbox): detect sing-box 1.14+ and omit deprecated download_detour on remote rule-sets when http_clients is active
- fix(action): pre-seed /etc/config/zapret2 with enabled=0 via core.uci before package install to prevent conflicting upstream daemon startup
- test(ruleset): add verification tests for mieru download_detour and dial_detour rejection
This commit is contained in:
Dushnilin
2026-09-10 20:18:00 +03:00
parent a254cfc8b6
commit bc4f12913f
11 changed files with 233 additions and 28 deletions
+10 -1
View File
@@ -1464,7 +1464,16 @@ function install_zapret2(action, target_tag) {
let hosts_content = read_file("/etc/hosts") || "";
if (index(hosts_content, "::1") < 0)
command_success("printf '\\n::1 localhost ip6-localhost ip6-loopback\\n' >> /etc/hosts");
command_success("printf '\n::1 localhost ip6-localhost ip6-loopback\n' >> /etc/hosts");
if (!file_exists("/etc/config/zapret2"))
write_file("/etc/config/zapret2", "config zapret2 'main'\n\toption enabled '0'\n");
if (uci_core.available()) {
if (!uci_core.exists("zapret2.main"))
uci_core.set_section("zapret2.main", "zapret2");
uci_core.set("zapret2.main.enabled", "0");
uci_core.commit("zapret2");
}
run_logged("Updating package lists before " + label + " package installation", pkg_list_update_command());
+8 -1
View File
@@ -164,7 +164,12 @@ function constants_map() {
c.COMMUNITY_SERVICES = env("COMMUNITY_SERVICES", "russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube hdrezka tiktok google_ai google_play hodca discord meta twitter cloudflare cloudfront digitalocean hetzner ovh telegram roblox ads_hagezi_pro supercell github");
c.ZAPRET_PROVIDER_BASE_DIR = env("ZAPRET_PROVIDER_BASE_DIR", "/opt/zapret");
c.ZAPRET_PROVIDER_NFQWS_BIN = env("ZAPRET_PROVIDER_NFQWS_BIN", c.ZAPRET_PROVIDER_BASE_DIR + "/nfq/nfqws");
let default_nfqws_bin = c.ZAPRET_PROVIDER_BASE_DIR + "/nfq/nfqws";
if (fs.stat(default_nfqws_bin) == null && fs.stat(c.ZAPRET_PROVIDER_BASE_DIR + "/nfqws") != null)
default_nfqws_bin = c.ZAPRET_PROVIDER_BASE_DIR + "/nfqws";
else if (fs.stat(default_nfqws_bin) == null && fs.stat("/usr/bin/nfqws") != null)
default_nfqws_bin = "/usr/bin/nfqws";
c.ZAPRET_PROVIDER_NFQWS_BIN = env("ZAPRET_PROVIDER_NFQWS_BIN", default_nfqws_bin);
c.ZAPRET_PROVIDER_FILES_DIR = env("ZAPRET_PROVIDER_FILES_DIR", c.ZAPRET_PROVIDER_BASE_DIR + "/files");
c.ZAPRET_PROVIDER_IPSET_DIR = env("ZAPRET_PROVIDER_IPSET_DIR", c.ZAPRET_PROVIDER_BASE_DIR + "/ipset");
c.ZAPRET_LEGACY_RUNTIME_BASE_DIR = env("ZAPRET_LEGACY_RUNTIME_BASE_DIR", "/var/run/tachyon/zapret-runtime");
@@ -187,6 +192,8 @@ function constants_map() {
let default_nfqws2_bin = c.ZAPRET2_PROVIDER_BASE_DIR + "/nfq2/nfqws2";
if (fs.stat(default_nfqws2_bin) == null && fs.stat(c.ZAPRET2_PROVIDER_BASE_DIR + "/nfq/nfqws2") != null)
default_nfqws2_bin = c.ZAPRET2_PROVIDER_BASE_DIR + "/nfq/nfqws2";
else if (fs.stat(default_nfqws2_bin) == null && fs.stat(c.ZAPRET2_PROVIDER_BASE_DIR + "/nfqws2") != null)
default_nfqws2_bin = c.ZAPRET2_PROVIDER_BASE_DIR + "/nfqws2";
else if (fs.stat(default_nfqws2_bin) == null && fs.stat("/usr/bin/nfqws2") != null)
default_nfqws2_bin = "/usr/bin/nfqws2";
c.ZAPRET2_PROVIDER_NFQWS2_BIN = env("ZAPRET2_PROVIDER_NFQWS2_BIN", default_nfqws2_bin);
+44 -2
View File
@@ -1466,6 +1466,7 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po
if (!nft_create_chain(table, "dns_redirect", "{ type nat hook prerouting priority -100; policy accept; }") ||
!nft_create_chain(table, "mangle", "{ type filter hook prerouting priority -149; policy accept; }") ||
!nft_create_chain(table, "raw_output", "{ type filter hook output priority -300; policy accept; }") ||
!nft_create_chain(table, "mangle_output", "{ type route hook output priority -150; policy accept; }") ||
!nft_create_priority_chains(table) ||
!nft_create_chain(table, "parental_control", "{ }") ||
@@ -1475,6 +1476,11 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po
!nft_create_chain(table, "proxy", "{ type filter hook prerouting priority -100; policy accept; }"))
return false;
if (!nft_add_rule(table, "raw_output", [ "meta", "mark", "&", "0x40000000", "==", "0x40000000", "notrack" ]) ||
!nft_add_rule(table, "raw_output", [ "meta", "mark", "&", "0x20000000", "==", "0x20000000", "notrack" ]) ||
!nft_add_rule(table, "raw_output", [ "meta", "skuid", "{ 2147483647, 65534 }", "notrack" ]))
return false;
if (!nft_add_rule(table, "dns_redirect", [ "iifname", "@" + as_string(interface_set), "ip", "saddr", "@" + DNS_SOURCE_SET, "tcp", "dport", "53", "counter", "redirect", "to", ":" + as_string(runtime_constants.SOURCE_DNS_INBOUND_PORT) ]) ||
!nft_add_rule(table, "dns_redirect", [ "iifname", "@" + as_string(interface_set), "ip", "saddr", "@" + DNS_SOURCE_SET, "udp", "dport", "53", "counter", "redirect", "to", ":" + as_string(runtime_constants.SOURCE_DNS_INBOUND_PORT) ]) ||
!nft_add_rule(table, "dns_redirect", [ "iifname", "@" + as_string(interface_set), "ip6", "saddr", "@" + DNS_SOURCE6_SET, "tcp", "dport", "53", "counter", "redirect", "to", ":" + as_string(runtime_constants.SOURCE_DNS_INBOUND_PORT) ]) ||
@@ -1594,6 +1600,9 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po
!nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "udp", "tproxy", "ip", "to", ":" + as_string(tproxy_port), "counter" ]) ||
!nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "tcp", "tproxy", "ip6", "to", core_ip.format_ipv6_tproxy_target(tproxy6_address, tproxy_port), "counter" ]) ||
!nft_add_rule(table, "proxy", [ "meta", "mark", "&", fakeip_mark, "==", fakeip_mark, "meta", "l4proto", "udp", "tproxy", "ip6", "to", core_ip.format_ipv6_tproxy_target(tproxy6_address, tproxy_port), "counter" ]) ||
!nft_add_rule(table, "mangle_output", [ "meta", "mark", "&", "0x40000000", "==", "0x40000000", "counter", "return" ]) ||
!nft_add_rule(table, "mangle_output", [ "meta", "mark", "&", "0x20000000", "==", "0x20000000", "counter", "return" ]) ||
!nft_add_rule(table, "mangle_output", [ "meta", "skuid", "{ 2147483647, 65534 }", "counter", "return" ]) ||
!nft_add_rule(table, "mangle_output", [ "ip", "daddr", "@" + as_string(localv4_set), "return" ]) ||
!nft_add_rule(table, "mangle_output", [ "ip6", "daddr", "@" + as_string(localv6_set), "return" ]) ||
!nft_add_rule(table, "mangle_output", [ "meta", "mark", outbound_mark, "counter", "return" ]) ||
@@ -1601,6 +1610,8 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po
return false;
if (!nft_create_chain(table, "mangle_forward", "{ type filter hook forward priority -150; policy accept; }") ||
!nft_add_rule(table, "mangle_forward", [ "meta", "mark", "&", "0x40000000", "==", "0x40000000", "counter", "return" ]) ||
!nft_add_rule(table, "mangle_forward", [ "meta", "mark", "&", "0x20000000", "==", "0x20000000", "counter", "return" ]) ||
!nft_add_rule(table, "mangle_forward", [ "jump", "guest_forward" ]) ||
!nft_add_rule(table, "mangle_forward", [ "jump", "parental_forward" ]))
return false;
@@ -1720,6 +1731,35 @@ function nft_provider_mark_hex(route_mark_base, index) {
return sprintf("0x%08x", base + index);
}
function resolve_provider_bin(action, provider_bin) {
if (provider_bin && file_executable(provider_bin))
return provider_bin;
let candidates = [];
if (action == "zapret2") {
candidates = [
getenv("ZAPRET2_NFQWS2_BIN"),
getenv("ZAPRET2_PROVIDER_NFQWS2_BIN"),
"/opt/zapret2/nfq2/nfqws2",
"/opt/zapret2/nfq/nfqws2",
"/opt/zapret2/nfqws2",
"/usr/bin/nfqws2"
];
} else if (action == "zapret") {
candidates = [
getenv("ZAPRET_NFQWS_BIN"),
getenv("ZAPRET_PROVIDER_NFQWS_BIN"),
"/opt/zapret/nfq/nfqws",
"/opt/zapret/nfqws",
"/usr/bin/nfqws"
];
}
for (let c in candidates) {
if (c && file_executable(c))
return c;
}
return provider_bin;
}
function nft_create_provider_output_rules_from_sections(sections, table, action, provider_bin, route_mark_base, queue_base, desync_mark, desync_mark_postnat) {
if (!file_executable(provider_bin))
return true;
@@ -1741,6 +1781,7 @@ function nft_create_provider_output_rules_from_sections(sections, table, action,
if (!added) {
if (!nft_add_rule(table, "mangle_output", [ "meta", "mark", "&", desync_mark, "==", desync_mark, "return" ]) ||
!nft_add_rule(table, "mangle_output", [ "meta", "mark", "&", desync_mark_postnat, "==", desync_mark_postnat, "return" ]) ||
!nft_add_rule(table, "mangle_output", [ "meta", "skuid", "{ 2147483647, 65534 }", "counter", "return" ]) ||
!nft_add_rule(table, "mangle_forward", [ "meta", "mark", "&", desync_mark, "==", desync_mark, "return" ]) ||
!nft_add_rule(table, "mangle_forward", [ "meta", "mark", "&", desync_mark_postnat, "==", desync_mark_postnat, "return" ]))
return false;
@@ -2827,8 +2868,9 @@ function nft_populate_runtime_sets_from_uci(populate_enabled, deferred_section_n
let common6 = default_arg(common6_set, "tachyon_subnets6");
let ip_port6 = default_arg(ip_port6_set, "tachyon_ip6_ports");
let frange6 = "fc00::/18";
let taddr6 = "[::1]:1602";
nft_rebuild_runtime_from_uci(rt_table, table, localv4, common, port, ip_port, iface, fmark, omark, frange4, tport, "", "", "", "", "", "/opt/zapret2/bin/nfqws2", "0x02000000", "200", "0x00000002", "0x00000004", localv6, common6, ip_port6, frange6, taddr6);
let zapret_bin = resolve_provider_bin("zapret", "");
let zapret2_bin = resolve_provider_bin("zapret2", "");
nft_rebuild_runtime_from_uci(rt_table, table, localv4, common, port, ip_port, iface, fmark, omark, frange4, tport, zapret_bin, "0x01000000", "4000", "0x40000000", "0x20000000", zapret2_bin, "0x02000000", "4300", "0x40000000", "0x20000000", localv6, common6, ip_port6, frange6, taddr6);
}
return nft_populate_runtime_sets_from_sections(uci_sections("section"), populate_enabled, deferred_section_names, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, common6_set, ip_port6_set, localv6_set);
@@ -472,20 +472,19 @@ function start_runtime(cfg) {
let started_sections = [];
let index_value = 1;
for (let section in sections) {
let name = section_name(section);
if (!start_rule(cfg, section, index_value)) {
log_message("Rolling back " + as_string(length(started_sections)) + " started " + cfg.binary_name + " rules.", "warn");
for (let started in started_sections) {
let name = section_name(started);
let pidfile = cfg.pid_dir + "/" + name + ".pid";
let child_pidfile = cfg.child_pid_dir + "/" + name + ".pid";
kill_pidfile_process(pidfile, "9");
kill_pidfile_process(child_pidfile, "9");
}
exit(1);
log_message("Failed to start " + cfg.binary_name + " rule '" + name + "'; skipping rule to preserve service availability.", "warn");
} else {
push(started_sections, section);
}
push(started_sections, section);
index_value++;
}
if (length(started_sections) == 0 && length(sections) > 0) {
log_message("Failed to start any " + cfg.binary_name + " rules out of " + as_string(length(sections)) + " configured. Aborted.", "fatal");
exit(1);
}
}
function runtime_tag(base, postfix) {
@@ -62,11 +62,19 @@ function resolve_blobs(args_str) {
return result;
}
function safe_str(val) {
if (type(as_string) == "function")
return as_string(val);
if (val == null)
return "";
return "" + val;
}
function prepare_strategy_args(raw_opt) {
let raw_str = as_string(raw_opt);
let raw_str = safe_str(raw_opt);
let extra_args = resolve_blobs(raw_str);
let filter_prefix = [];
if (index(raw_str, "--filter-tcp") < 0 && index(raw_str, "--filter-l7") < 0) {
if (index(raw_str, "--filter-tcp") < 0 && index(raw_str, "--filter-l7") < 0 && index(raw_str, "--filter-udp") < 0) {
push(filter_prefix, "--filter-tcp=443");
push(filter_prefix, "--filter-l7=tls");
push(filter_prefix, "--payload=tls_client_hello");
+4 -1
View File
@@ -108,7 +108,10 @@ function get_system_status() {
status_obj.singbox_variant = sb_variant;
// zapret2 status
let z2_installed = fs.stat("/opt/zapret2/nfq2/nfqws2") != null;
let z2_installed = fs.stat("/opt/zapret2/nfq2/nfqws2") != null ||
fs.stat("/opt/zapret2/nfq/nfqws2") != null ||
fs.stat("/opt/zapret2/nfqws2") != null ||
fs.stat("/usr/bin/nfqws2") != null;
status_obj.zapret2_installed = z2_installed;
if (z2_installed) {
let z2_running = command_success_from_args(["pidof", "nfqws2"]);
+35 -2
View File
@@ -597,7 +597,40 @@ function release_start_subscription_update_lock() {
start_subscription_update_lock_held = false;
}
function resolve_zapret_bin() {
let candidate_bins = [
getenv("ZAPRET_NFQWS_BIN"),
getenv("ZAPRET_PROVIDER_NFQWS_BIN"),
ZAPRET_PROVIDER_NFQWS_BIN,
"/opt/zapret/nfq/nfqws",
"/opt/zapret/nfqws",
"/usr/bin/nfqws"
];
for (let b in candidate_bins) {
if (b && fs.stat(b) != null) return b;
}
return ZAPRET_PROVIDER_NFQWS_BIN;
}
function resolve_zapret2_bin() {
let candidate_bins = [
getenv("ZAPRET2_NFQWS2_BIN"),
getenv("ZAPRET2_PROVIDER_NFQWS2_BIN"),
ZAPRET2_PROVIDER_NFQWS2_BIN,
"/opt/zapret2/nfq2/nfqws2",
"/opt/zapret2/nfq/nfqws2",
"/opt/zapret2/nfqws2",
"/usr/bin/nfqws2"
];
for (let b in candidate_bins) {
if (b && fs.stat(b) != null) return b;
}
return ZAPRET2_PROVIDER_NFQWS2_BIN;
}
function nft_rebuild_runtime() {
let zapret_bin = resolve_zapret_bin();
let zapret2_bin = resolve_zapret2_bin();
return module_status(NFT_UC, [
"nft-rebuild-runtime-from-uci",
RT_TABLE_NAME,
@@ -611,12 +644,12 @@ function nft_rebuild_runtime() {
NFT_OUTBOUND_MARK,
SB_FAKEIP_INET4_RANGE,
SB_TPROXY_INBOUND_PORT,
ZAPRET_PROVIDER_NFQWS_BIN,
zapret_bin,
ZAPRET_ROUTE_MARK_BASE,
ZAPRET_QUEUE_BASE,
ZAPRET_DESYNC_MARK,
ZAPRET_DESYNC_MARK_POSTNAT,
ZAPRET2_PROVIDER_NFQWS2_BIN,
zapret2_bin,
ZAPRET2_ROUTE_MARK_BASE,
ZAPRET2_QUEUE_BASE,
ZAPRET2_DESYNC_MARK,
+6 -2
View File
@@ -93,8 +93,10 @@ function emit_reload_plan(previous, current, context) {
needs.sing_box_reload = true;
}
if (changed.zapret_runtime)
if (changed.zapret_runtime) {
needs.zapret_restart = true;
needs.nft_rebuild = true;
}
if (changed.zapret2_queue) {
needs.zapret2_restart = true;
@@ -102,8 +104,10 @@ function emit_reload_plan(previous, current, context) {
needs.sing_box_reload = true;
}
if (changed.zapret2_runtime)
if (changed.zapret2_runtime) {
needs.zapret2_restart = true;
needs.nft_rebuild = true;
}
if (changed.byedpi_runtime)
needs.byedpi_restart = true;
+34 -3
View File
@@ -404,6 +404,32 @@ function tproxy_inbound_matcher() {
return [ runtime_constants.TPROXY_INBOUND_TAG, runtime_constants.TPROXY_INBOUND6_TAG ];
}
function is_sb_1_14_plus_detected(sb_version_val) {
if (sb_version_val == null || sb_version_val == "") {
let sb_version_file = getenv("SB_VERSION_STATE_FILE") || "/etc/tachyon/sing-box-version";
sb_version_val = trim(fs.readfile(sb_version_file) || "");
if (sb_version_val == "") {
let sb_ui_cache = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_FILE") || "/var/run/tachyon/ui-state/sing-box-version";
sb_version_val = trim(fs.readfile(sb_ui_cache) || "");
}
if (sb_version_val == "") {
try {
let pipe = fs.popen("sing-box version 2>/dev/null", "r");
if (pipe) {
let out = pipe.read("all");
pipe.close();
let m = match(out, /sing-box version ([^\s]+)/);
if (m)
sb_version_val = m[1];
}
} catch (e) {}
}
}
if (sb_version_val != "")
return match(sb_version_val, /^v?1\.(1[4-9]|[2-9][0-9])\./) != null;
return false;
}
function base_config(settings, service_address, runtime_context) {
runtime_context = object_or_empty(runtime_context);
let log_level = option(settings, "log_level", "warn");
@@ -547,9 +573,7 @@ function base_config(settings, service_address, runtime_context) {
? (match(sb_version_val, /^v?1\.1[0-3]\./) != null)
: !is_extended_variant;
let is_sb_1_14_plus = sb_version_val != ""
? (match(sb_version_val, /^v?1\.(1[4-9]|[2-9][0-9])\./) != null)
: false;
let is_sb_1_14_plus = is_sb_1_14_plus_detected(sb_version_val);
let route_section = runtime_route.config(settings, runtime_context);
if (is_sb_1_14_plus)
@@ -1272,6 +1296,12 @@ function generate_config(output_path, service_address, mwan3_active, supports_xh
add_content_blocking(config);
if (type(config.http_clients) == "array" && length(config.http_clients) > 0) {
let detour = download_detour_tag(settings, "lists");
if (detour != "" && generator_routes.is_valid_detour(config, detour))
config.http_clients[0].dial_detour = detour;
}
assert_unique_outbound_tags(config);
strip_internal_fields(config);
if (!atomic_write_json_file(output_path, config)) {
@@ -1401,6 +1431,7 @@ ctx.runtime_generate_unsupported = runtime_generate_unsupported;
ctx.uci_bin_to_hex = uci_bin_to_hex;
ctx.download_detour_tag = download_detour_tag;
ctx.atomic_write_json_file = atomic_write_json_file;
ctx.is_sb_1_14_plus = is_sb_1_14_plus_detected;
generator_outbounds.init(ctx);
generator_routes.init(ctx);
@@ -637,6 +637,25 @@ function add_priority_group_outbound(config, section, group_id, urltest_candidat
};
}
function is_mieru_detour(config, tag, depth) {
depth = int(depth || 0);
if (depth > 5 || tag == null || tag == "")
return false;
for (let out in array_or_empty(config.outbounds)) {
if (type(out) == "object" && out.tag == tag) {
if (out.type == "mieru")
return true;
if ((out.type == "selector" || out.type == "urltest") && type(out.outbounds) == "array") {
for (let sub in out.outbounds) {
if (is_mieru_detour(config, sub, depth + 1))
return true;
}
}
}
}
return false;
}
function is_valid_detour(config, tag) {
if (tag == null || tag == "")
return false;
@@ -648,6 +667,8 @@ function is_valid_detour(config, tag) {
return false;
if (out.routing_mark != null)
return false;
if (out.type == "mieru" || is_mieru_detour(config, tag, 0))
return false;
return true;
}
}
@@ -805,7 +826,8 @@ function ensure_custom_ruleset(config, reference) {
url: runtime_rulesets.community_url(reference)
};
let detour = ctx.download_detour_tag(ctx.runtime_settings());
if (is_valid_detour(config, detour))
let is_1_14 = ctx.is_sb_1_14_plus && ctx.is_sb_1_14_plus();
if (!is_1_14 && is_valid_detour(config, detour))
rule_set.download_detour = detour;
rule_set.update_interval = remote_ruleset_update_interval();
push(config.route.rule_set, rule_set);
@@ -842,7 +864,8 @@ function ensure_custom_ruleset(config, reference) {
url: reference
};
let detour = ctx.download_detour_tag(ctx.runtime_settings());
if (is_valid_detour(config, detour))
let is_1_14 = ctx.is_sb_1_14_plus && ctx.is_sb_1_14_plus();
if (!is_1_14 && is_valid_detour(config, detour))
rule_set.download_detour = detour;
rule_set.update_interval = remote_ruleset_update_interval();
push(config.route.rule_set, rule_set);
@@ -895,7 +918,8 @@ function ensure_community_ruleset(config, section_name, community) {
detour = sec_out;
}
}
if (is_valid_detour(config, detour))
let is_1_14 = ctx.is_sb_1_14_plus && ctx.is_sb_1_14_plus();
if (!is_1_14 && is_valid_detour(config, detour))
rule_set.download_detour = detour;
push(config.route.rule_set, rule_set);
}
@@ -1945,5 +1969,6 @@ return {
section_by_name,
add_server_routes,
outbound_supports_udp,
push_section_route_rule
push_section_route_rule,
is_valid_detour
};
+44
View File
@@ -147,4 +147,48 @@ if grep -q '"download_detour": "zapret_sec-out"' "$output_zapret"; then
fail "zapret-out must never be selected as download_detour due to routing_mark"
fi
# 5. Verify mieru outbound is rejected as download_detour and http_clients dial_detour
cat >"$WORK_DIR/fixture_mieru.json" <<'JSON'
{
"settings": {
".name": "settings",
".type": "settings",
"enabled": "1",
"dns_type": "udp",
"dns_server": "1.1.1.1",
"service_listen_address": "127.0.0.1"
},
"section": [
{
".name": "mieru_sec",
".type": "section",
"enabled": "1",
"action": "connection",
"outbound_jsons": [ "{\"type\":\"mieru\",\"server\":\"1.2.3.4\",\"server_port\":443}" ],
"community_lists": [ "google_ai" ]
}
]
}
JSON
output_mieru="$WORK_DIR/out_mieru.json"
mkdir -p "$output_mieru.section-cache" "$output_mieru.rulesets"
ucode -L "$TACHYON_LIB" "$GENERATOR_UC" generate-config-fixture \
"$WORK_DIR/fixture_mieru.json" "$output_mieru" "127.0.0.1" "0" "1"
if grep -q '"download_detour": "mieru_sec-out"' "$output_mieru"; then
fail "mieru outbound must never be assigned to rule_set download_detour (early startup crash)"
fi
output_mieru_v14="$WORK_DIR/out_mieru_v14.json"
mkdir -p "$output_mieru_v14.section-cache" "$output_mieru_v14.rulesets"
SB_VERSION_STATE_FILE="$WORK_DIR/sb_v14" \
ucode -L "$TACHYON_LIB" "$GENERATOR_UC" generate-config-fixture \
"$WORK_DIR/fixture_mieru.json" "$output_mieru_v14" "127.0.0.1" "0" "1"
if grep -q '"dial_detour": "mieru_sec-out"' "$output_mieru_v14"; then
fail "mieru outbound must never be assigned as http_clients dial_detour"
fi
printf "ruleset download detour checks passed\n"