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:
@@ -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");
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user