fix: корректный порядок применения секций сверху вниз и совместимость со всеми ядрами sing-box (1.3.21)

- Исправлена совместимость с Official (1.13.x), Tiny, LX и Extended ядрами sing-box
- Устранена фатальная ошибка unknown field store_dns на sing-box 1.13.x (автоопределение store_rdrc vs store_dns)
- Добавлена плавная деградация в runtime при неподдерживаемых параметрах cache_file и неизвестных транспортах
- Разделены правила маршрутизации доменов/наборов правил и IP CIDR, устраняя конфликт логики AND в sing-box 1.14+
- Исключены подсети общих CDN (Cloudflare, CloudFront, Hetzner, OVH, DigitalOcean) из nftables L4 для предотвращения перехвата сторонних ресурсов (включая Discord) вышестоящими секциями
- Обеспечено строгое соблюдение порядка следования секций сверху вниз во всех конфигурациях
This commit is contained in:
Dushnilin
2026-09-06 08:33:18 +03:00
parent 11925e7ec5
commit 6d921d963c
5 changed files with 135 additions and 59 deletions
+20 -1
View File
@@ -515,7 +515,26 @@ function base_config(settings, service_address, runtime_context) {
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) || "");
}
let use_legacy_rdrc = match(sb_version_val, /^v?1\.1[0-3]\./) != null;
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) {}
}
let sb_variant_file = getenv("SB_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant";
let sb_variant_val = trim(fs.readfile(sb_variant_file) || "");
let is_extended_variant = sb_variant_val == "extended" || sb_variant_val == "extended-compressed";
let use_legacy_rdrc = sb_version_val != ""
? (match(sb_version_val, /^v?1\.1[0-3]\./) != null)
: !is_extended_variant;
let cache_file_section = {
enabled: true,
path: cache_path,
@@ -1255,8 +1255,6 @@ function add_combined_route_for_section(config, section) {
let ensured = ensure_community_ruleset(config, section_name, as_string(community));
push(rule_set_tags, ensured.tag);
push(dns_rule_set_tags, ensured.tag);
for (let cidr in load_community_subnet_cidrs(community))
push(ip_cidr, cidr);
}
for (let reference in connections.rule_sets(section)) {
let ensured = ensure_custom_ruleset(config, as_string(reference));
@@ -1286,51 +1284,71 @@ function add_combined_route_for_section(config, section) {
let target = runtime_route.target(section, outbound_tag(section_name));
if (target.unsupported)
ctx.runtime_generate_unsupported(target.unsupported);
let route_rule = {
action: target.action,
inbound: tproxy_inbound_matcher()
};
if (target.outbound)
route_rule.outbound = target.outbound;
add_domain_array(route_rule, "domain", domain);
add_domain_array(route_rule, "domain_suffix", domain_suffix);
add_domain_array(route_rule, "domain_keyword", domain_keyword);
add_domain_array(route_rule, "domain_regex", domain_regex);
if (length(ip_cidr) > 0)
route_rule.ip_cidr = ip_cidr;
if (length(source_ip_cidr) > 0)
route_rule.source_ip_cidr = source_ip_cidr;
add_port_matchers(route_rule, section);
add_dscp_matchers(route_rule, section);
add_protocol_matchers(route_rule, section);
if (length(rule_set_tags) > 0)
route_rule.rule_set = single_or_array(rule_set_tags);
let has_domain_or_ruleset = length(domain) > 0 || length(domain_suffix) > 0 ||
length(domain_keyword) > 0 || length(domain_regex) > 0 ||
length(rule_set_tags) > 0;
let has_ip_cidr = length(ip_cidr) > 0;
let create_section_route_rule = function() {
let r = {
action: target.action,
inbound: tproxy_inbound_matcher()
};
if (target.outbound)
r.outbound = target.outbound;
if (length(source_ip_cidr) > 0)
r.source_ip_cidr = source_ip_cidr;
add_port_matchers(r, section);
add_dscp_matchers(r, section);
add_protocol_matchers(r, section);
return r;
};
if (has_domain_or_ruleset) {
let domain_rule = create_section_route_rule();
add_domain_array(domain_rule, "domain", domain);
add_domain_array(domain_rule, "domain_suffix", domain_suffix);
add_domain_array(domain_rule, "domain_keyword", domain_keyword);
add_domain_array(domain_rule, "domain_regex", domain_regex);
if (length(rule_set_tags) > 0)
domain_rule.rule_set = single_or_array(rule_set_tags);
let resolve = runtime_route.resolve_rule_for_section(section, domain_rule);
if (type(resolve) == "object" && resolve.warning)
warn(resolve.warning, "\n");
else if (type(resolve) == "object" && resolve.rule)
push(config.route.rules, resolve.rule);
push(config.route.rules, domain_rule);
}
if (has_ip_cidr) {
let ip_rule = create_section_route_rule();
ip_rule.ip_cidr = ip_cidr;
push(config.route.rules, ip_rule);
}
let country_list = connections.geoip_country_list(section);
let country_mode = connections.geoip_country_mode(section);
if (target.outbound && length(country_list) > 0) {
let rule_set_tags = [];
let geo_tags = [];
for (let cc in country_list) {
let ip_ruleset = ensure_community_ruleset(config, section_name, "geoip_" + cc);
if (ip_ruleset && ip_ruleset.tag)
push(rule_set_tags, ip_ruleset.tag);
push(geo_tags, ip_ruleset.tag);
if (cc == "ru") {
let site_ruleset = ensure_community_ruleset(config, section_name, "geosite_ru");
if (site_ruleset && site_ruleset.tag)
push(rule_set_tags, site_ruleset.tag);
push(geo_tags, site_ruleset.tag);
}
}
if (length(rule_set_tags) > 0) {
let geoip_route_rule = {
action: target.action,
inbound: tproxy_inbound_matcher(),
rule_set: rule_set_tags,
outbound: target.outbound
};
if (length(geo_tags) > 0) {
let geoip_route_rule = create_section_route_rule();
geoip_route_rule.rule_set = geo_tags;
if (country_mode == "exclude")
geoip_route_rule.invert = true;
@@ -1338,19 +1356,13 @@ function add_combined_route_for_section(config, section) {
}
}
let has_route_matchers = route_rule.domain != null || route_rule.domain_suffix != null ||
route_rule.domain_keyword != null || route_rule.domain_regex != null ||
route_rule.ip_cidr != null || route_rule.source_ip_cidr != null ||
route_rule.port != null || route_rule.port_range != null ||
route_rule.protocol != null || route_rule.dscp != null ||
route_rule.rule_set != null;
if (has_route_matchers) {
let resolve = runtime_route.resolve_rule_for_section(section, route_rule);
if (type(resolve) == "object" && resolve.warning)
warn(resolve.warning, "\n");
else if (type(resolve) == "object" && resolve.rule)
push(config.route.rules, resolve.rule);
push(config.route.rules, route_rule);
if (!has_domain_or_ruleset && !has_ip_cidr && length(country_list) == 0) {
let fallback_rule = create_section_route_rule();
let has_any_matcher = fallback_rule.source_ip_cidr != null ||
fallback_rule.port != null || fallback_rule.port_range != null ||
fallback_rule.protocol != null || fallback_rule.dscp != null;
if (has_any_matcher)
push(config.route.rules, fallback_rule);
}
let rewrite_ttl = int_option(ctx.runtime_settings(), "dns_rewrite_ttl", "60");
+51 -13
View File
@@ -993,21 +993,59 @@ function init_config(populate_nft, caches_prepared, no_refresh) {
stripped = true;
}
}
else if (match(check_result.reason, /header_protection_key/)) {
log_message("Installed sing-box rejected header_protection_key: " + check_result.reason + "; stripping and retrying", "warn");
else if (match(check_result.reason, /experimental\.cache_file\.store_dns/)) {
log_message("Installed sing-box rejected store_dns: " + check_result.reason + "; switching to store_rdrc and retrying", "warn");
let cfg_text = as_string(fs.readfile(temp_config) || "");
let cfg = length(cfg_text) > 0 ? json(cfg_text) : null;
if (type(cfg) == "object") {
if (type(cfg.endpoints) == "array") {
for (let ep in cfg.endpoints) {
if (type(ep) == "object" && ep.header_protection_key != null)
delete ep.header_protection_key;
}
}
if (type(cfg.inbounds) == "array") {
for (let inb in cfg.inbounds) {
if (type(inb) == "object" && inb.header_protection_key != null)
delete inb.header_protection_key;
if (type(cfg) == "object" && type(cfg.experimental) == "object" && type(cfg.experimental.cache_file) == "object") {
delete cfg.experimental.cache_file.store_dns;
cfg.experimental.cache_file.store_rdrc = true;
write_file(temp_config, sprintf("%J", cfg));
stripped = true;
}
}
else if (match(check_result.reason, /experimental\.cache_file\.store_rdrc/)) {
log_message("Installed sing-box rejected store_rdrc: " + check_result.reason + "; switching to store_dns and retrying", "warn");
let cfg_text = as_string(fs.readfile(temp_config) || "");
let cfg = length(cfg_text) > 0 ? json(cfg_text) : null;
if (type(cfg) == "object" && type(cfg.experimental) == "object" && type(cfg.experimental.cache_file) == "object") {
delete cfg.experimental.cache_file.store_rdrc;
cfg.experimental.cache_file.store_dns = true;
write_file(temp_config, sprintf("%J", cfg));
stripped = true;
}
}
else if (match(check_result.reason, /experimental\.cache_file\.(\w+): json: unknown field/)) {
let cf_m = match(check_result.reason, /experimental\.cache_file\.(\w+): json: unknown field/);
let unknown_field = cf_m[1];
log_message("Installed sing-box does not support cache_file field '" + unknown_field + "'; stripping and retrying", "warn");
let cfg_text = as_string(fs.readfile(temp_config) || "");
let cfg = length(cfg_text) > 0 ? json(cfg_text) : null;
if (type(cfg) == "object" && type(cfg.experimental) == "object" && type(cfg.experimental.cache_file) == "object") {
delete cfg.experimental.cache_file[unknown_field];
write_file(temp_config, sprintf("%J", cfg));
stripped = true;
}
}
else if (match(check_result.reason, /outbounds\[\d+\]\.transport: unknown transport type: (\w+)/)) {
let tr_m = match(check_result.reason, /outbounds\[\d+\]\.transport: unknown transport type: (\w+)/);
let bad_transport = tr_m[1];
let out_idx_m = match(check_result.reason, /outbounds\[(\d+)\]/);
let out_idx = out_idx_m ? int(out_idx_m[1]) : -1;
log_message("Installed sing-box does not support transport '" + bad_transport + "' on outbound index " + out_idx + "; stripping outbound and retrying", "warn");
let cfg_text = as_string(fs.readfile(temp_config) || "");
let cfg = length(cfg_text) > 0 ? json(cfg_text) : null;
if (type(cfg) == "object" && type(cfg.outbounds) == "array" && out_idx >= 0 && out_idx < length(cfg.outbounds)) {
let bad_tag = cfg.outbounds[out_idx].tag;
splice(cfg.outbounds, out_idx, 1);
for (let outb in cfg.outbounds) {
if (type(outb) == "object" && type(outb.outbounds) == "array") {
let filtered = [];
for (let tag in outb.outbounds) {
if (tag != bad_tag)
push(filtered, tag);
}
outb.outbounds = filtered;
}
}
write_file(temp_config, sprintf("%J", cfg));