feat: поддержка AmneziaWG 3.1, keepalive ranges, безопасный ruleset HTTP client и Source-Aware DNS

This commit is contained in:
Dushnilin
2026-09-07 10:14:12 +03:00
parent e8c1d9d382
commit dd6b877adb
19 changed files with 525 additions and 69 deletions
@@ -508,6 +508,51 @@ function rule_sets_value(section) {
return list_option_value_from_array(rule_sets(section));
}
function combined_domain_condition_text(section) {
if (type(raw_option(section, "domain")) != "array") {
let value = option(section, "domain", "");
if (value != "")
return value;
}
return option(section, "domain_suffix_text", "");
}
function rule_config_or_null() {
try {
return require("config.rule");
} catch (e) {
return null;
}
}
function rule_condition_csv(section, key, kind) {
let rule_config = rule_config_or_null();
if (!rule_config)
return "";
return rule_config.rule_condition_csv_value(
key,
kind,
option(section, key + "_text_mode", "0"),
option(section, "conditions_text_mode", "0"),
option(section, key + "_text", ""),
option(section, key, ""),
combined_domain_condition_text(section),
option(section, "domain_suffix", "")
);
}
function has_dns_matchers(section) {
return rule_condition_csv(section, "domain", "domains") != "" ||
rule_condition_csv(section, "domain_suffix", "domains") != "" ||
rule_condition_csv(section, "domain_keyword", "generic") != "" ||
rule_condition_csv(section, "domain_regex", "generic") != "" ||
length(community_lists(section)) > 0 ||
length(rule_sets(section)) > 0 ||
length(rule_sets_with_subnets(section)) > 0 ||
option(section, "domain_ip_lists", "") != "";
}
const DSCP_ALIASES = {
"cs0": 0, "cs1": 8, "cs2": 16, "cs3": 24, "cs4": 32, "cs5": 40, "cs6": 48, "cs7": 56,
"ef": 46,
@@ -1359,5 +1404,6 @@ return {
geoip_country_value,
subscription_download_targets,
subscription_download_target_port,
has_dns_matchers,
item_index_from_cursor
};
+11 -2
View File
@@ -1388,8 +1388,17 @@ function validate_dns_action(section, sections, context) {
}
if (length(connections.rule_sets_with_subnets(section)) > 0)
fail_validation("DNS rule '" + name + "' can use domain-only rule sets, but subnet extraction is enabled. Disable 'Include IP addresses and subnets'. Aborted.");
if (!dns_action_has_domain_matchers(section))
fail_validation("DNS rule '" + name + "' must contain at least one domain condition or domain rule set. Aborted.");
let fully_routed_ips = list_option(section, "fully_routed_ips");
if (!dns_action_has_domain_matchers(section) && length(fully_routed_ips) == 0)
fail_validation("DNS rule '" + name + "' must contain at least one domain condition, domain rule set, or forced device. Aborted.");
for (let ip in fully_routed_ips) {
if (!core_ip.valid_ip_or_cidr(ip))
fail_validation("DNS rule '" + name + "' has an invalid forced device IP or subnet '" + ip + "'. Aborted.");
}
for (let ip in list_option(section, "source_ip_cidr")) {
if (!core_ip.valid_ip_or_cidr(ip))
fail_validation("DNS rule '" + name + "' has an invalid device filter IP or subnet '" + ip + "'. Aborted.");
}
if (!bool_option(section, "dns_detour_enabled", false))
return;
+82 -3
View File
@@ -9,8 +9,11 @@ let rule_config = require("config.rule");
let domain_config = require("config.domain");
let connections = require("config.connections");
let routing_rulesets = require("routing.rulesets");
let runtime_constants = require("singbox.constants");
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
const DNS_BLOCK_TARGET = getenv("SB_DNS_BLOCK_INBOUND_ADDRESS") || "127.0.0.43:1053";
const DNS_SOURCE_SET = runtime_constants.DNS_SOURCE_SET;
const DNS_SOURCE6_SET = runtime_constants.DNS_SOURCE6_SET;
let common_read_json_file = common.read_json_file;
let list_option = common.list_option;
@@ -1241,6 +1244,8 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po
!nft_create_inet_service_set(table, port_set) ||
!nft_create_ipv4_port_set(table, ip_port_set) ||
!nft_create_ipv6_port_set(table, ip_port6_set) ||
!nft_create_ipv4_set(table, DNS_SOURCE_SET) ||
!nft_create_ipv6_set(table, DNS_SOURCE6_SET) ||
!nft_create_ifname_set(table, interface_set))
return false;
@@ -1248,7 +1253,8 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po
if (!nft_add_set_elements(table, interface_set, interface))
return false;
if (!nft_create_chain(table, "mangle", "{ type filter hook prerouting priority -149; policy accept; }") ||
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, "mangle_output", "{ type route hook output priority -150; policy accept; }") ||
!nft_create_priority_chains(table) ||
!nft_create_chain(table, "parental_control", "{ }") ||
@@ -1257,7 +1263,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, "mangle", [ "ct", "status", "dnat", "return" ]) ||
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) ]) ||
!nft_add_rule(table, "dns_redirect", [ "iifname", "@" + as_string(interface_set), "ip6", "saddr", "@" + DNS_SOURCE6_SET, "udp", "dport", "53", "counter", "redirect", "to", ":" + as_string(runtime_constants.SOURCE_DNS_INBOUND_PORT) ]) ||
!nft_add_rule(table, "mangle", [ "ct", "status", "dnat", "return" ]) ||
!nft_add_rule(table, "mangle", [ "jump", "parental_control" ]))
return false;
@@ -2060,10 +2070,17 @@ function nft_rule_signature_body(body, section) {
let action = option(section, "action", "");
body = signature_add_value(body, "rule." + section_name + ".action", action);
if (action == "dns" || action == "hosts")
if (action == "hosts")
return body;
if (action == "dns") {
body = signature_add_value(body, "rule." + section_name + ".source_ip_cidr", section_rule_condition_csv(section, "source_ip_cidr", "subnets"));
body = signature_add_value(body, "rule." + section_name + ".source_aware_dns", connections.has_dns_matchers(section) ? "1" : "0");
body = signature_add_value(body, "rule." + section_name + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
return body;
}
body = signature_add_value(body, "rule." + section_name + ".ip_cidr", section_rule_condition_csv(section, "ip_cidr", "subnets"));
body = signature_add_value(body, "rule." + section_name + ".source_ip_cidr", section_rule_condition_csv(section, "source_ip_cidr", "subnets"));
body = signature_add_value(body, "rule." + section_name + ".source_aware_dns", connections.has_dns_matchers(section) ? "1" : "0");
body = signature_add_value(body, "rule." + section_name + ".ports", section_rule_ports_csv(section));
body = signature_add_value(body, "rule." + section_name + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
body = signature_add_value(body, "rule." + section_name + ".excluded_ips", option(section, "excluded_ips", ""));
@@ -2385,6 +2402,65 @@ function nft_add_community_subnet_file_for_fixture_section(fixture_path, section
return nft_add_community_subnet_file_for_section(fixture_section(fixture_path, section_name), service, filepath, table, common_set, ip_port_set, interface_set, discord_set, mark, chunk_size_text, common6_set, ip_port6_set, discord6_set);
}
function source_aware_dns_values(sections, deferred_sections) {
let seen = {};
let values = [];
for (let section in sections) {
if (!bool_option(section, "enabled", true) ||
deferred_sections[as_string(section[".name"])])
continue;
let action = section_action(section);
if (connections.has_dns_matchers(section)) {
for (let value in nft_csv_values(section_source_ip_values(section))) {
if (!seen[value]) {
seen[value] = true;
push(values, value);
}
}
}
if (action == "bypass" || action == "dns") {
for (let value in list_option(section, "fully_routed_ips")) {
value = trim(as_string(value));
if (value != "" && !seen[value]) {
seen[value] = true;
push(values, value);
}
}
}
if (action == "dns") {
for (let value in nft_csv_values(section_source_ip_values(section))) {
if (!seen[value]) {
seen[value] = true;
push(values, value);
}
}
}
}
return values;
}
function nft_add_source_aware_dns_sources(sections, deferred_sections, table) {
let values = source_aware_dns_values(sections, deferred_sections);
if (length(values) == 0)
return true;
return nft_add_csv_chunks_to_family_sets(
join(",", values),
table,
DNS_SOURCE_SET,
DNS_SOURCE6_SET,
"ips",
"",
5000
);
}
function nft_populate_runtime_sets_from_sections(sections, 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) {
if (!arg_bool(populate_enabled))
return true;
@@ -2393,6 +2469,9 @@ function nft_populate_runtime_sets_from_sections(sections, populate_enabled, def
let mangle_chain_context = {};
let inserted_fully_routed_ips = {};
if (!nft_add_source_aware_dns_sources(sections, deferred_sections, table))
return false;
for (let section in sections)
if (!nft_populate_runtime_set_for_section(section, deferred_sections, table, common_set, port_set, ip_port_set, interface_set, localv4_set, mark, mangle_chain_context, inserted_fully_routed_ips, common6_set, ip_port6_set, localv6_set))
return false;
+13 -4
View File
@@ -944,10 +944,17 @@ function nft_runtime_signature_body(settings, sections) {
let action = option(section, "action", "");
body = signature_add_value(body, "rule." + name + ".action", action);
if (action == "dns" || action == "hosts")
if (action == "hosts")
continue;
if (action == "dns") {
body = signature_add_value(body, "rule." + name + ".source_ip_cidr", section_rule_condition_csv(section, "source_ip_cidr", "subnets"));
body = signature_add_value(body, "rule." + name + ".source_aware_dns", connections.has_dns_matchers(section) ? "1" : "0");
body = signature_add_value(body, "rule." + name + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
continue;
}
body = signature_add_value(body, "rule." + name + ".ip_cidr", section_rule_condition_csv(section, "ip_cidr", "subnets"));
body = signature_add_value(body, "rule." + name + ".source_ip_cidr", section_rule_condition_csv(section, "source_ip_cidr", "subnets"));
body = signature_add_value(body, "rule." + name + ".source_aware_dns", connections.has_dns_matchers(section) ? "1" : "0");
body = signature_add_value(body, "rule." + name + ".ports", section_rule_ports_csv(section));
body = signature_add_value(body, "rule." + name + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
body = signature_add_value(body, "rule." + name + ".excluded_ips", option(section, "excluded_ips", ""));
@@ -1379,11 +1386,13 @@ function append_sing_box_rule_signature_body(body, section, sections) {
body = signature_add_value(body, prefix + ".domain_suffix", section_rule_condition_csv(section, "domain_suffix", "domains"));
body = signature_add_value(body, prefix + ".domain_keyword", section_rule_condition_csv(section, "domain_keyword", "generic"));
body = signature_add_value(body, prefix + ".domain_regex", section_rule_condition_csv(section, "domain_regex", "generic"));
if (action != "dns") {
if (action != "dns")
body = signature_add_value(body, prefix + ".ip_cidr", section_rule_condition_csv(section, "ip_cidr", "subnets"));
body = signature_add_value(body, prefix + ".source_ip_cidr", section_rule_condition_csv(section, "source_ip_cidr", "subnets"));
body = signature_add_value(body, prefix + ".source_ip_cidr", section_rule_condition_csv(section, "source_ip_cidr", "subnets"));
if (action != "dns")
body = signature_add_value(body, prefix + ".ports", section_rule_ports_csv(section));
body = signature_add_value(body, prefix + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
body = signature_add_value(body, prefix + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
if (action != "dns") {
body = signature_add_value(body, prefix + ".excluded_ips", option(section, "excluded_ips", ""));
body = signature_add_value(body, prefix + ".excluded_protocol", option(section, "excluded_protocol", ""));
body = signature_add_value(body, prefix + ".protocol", option(section, "protocol", ""));
@@ -18,6 +18,12 @@ const DNS_INBOUND_PORT = 53;
const DNS_BLOCK_INBOUND_TAG = "dns-block-in";
const DNS_BLOCK_INBOUND_ADDRESS = "127.0.0.43";
const DNS_BLOCK_INBOUND_PORT = 1053;
const SOURCE_DNS_INBOUND_TAG = "source-dns-in";
const SOURCE_DNS_INBOUND_ADDRESS = "::";
const SOURCE_DNS_INBOUND_PORT = 1603;
const DNSMASQ_DNS_SERVER_TAG = "dnsmasq-server";
const DNS_SOURCE_SET = "tachyon_dns_sources";
const DNS_SOURCE6_SET = "tachyon_dns_sources6";
const SERVICE_MIXED_INBOUND_TAG = "service-mixed-in";
const SERVICE_MIXED_INBOUND_ADDRESS = "127.0.0.1";
@@ -56,6 +62,8 @@ const RESERVED_TAGS = {
[TPROXY_INBOUND6_TAG]: true,
[DNS_INBOUND_TAG]: true,
[DNS_BLOCK_INBOUND_TAG]: true,
[SOURCE_DNS_INBOUND_TAG]: true,
[DNSMASQ_DNS_SERVER_TAG]: true,
[SERVICE_MIXED_INBOUND_TAG]: true,
[DIRECT_BYPASS_INBOUND_TAG]: true,
[DIRECT_BYPASS_OUTBOUND_TAG]: true,
@@ -106,6 +114,12 @@ return {
DNS_BLOCK_INBOUND_TAG,
DNS_BLOCK_INBOUND_ADDRESS,
DNS_BLOCK_INBOUND_PORT,
SOURCE_DNS_INBOUND_TAG,
SOURCE_DNS_INBOUND_ADDRESS,
SOURCE_DNS_INBOUND_PORT,
DNSMASQ_DNS_SERVER_TAG,
DNS_SOURCE_SET,
DNS_SOURCE6_SET,
SERVICE_MIXED_INBOUND_TAG,
SERVICE_MIXED_INBOUND_ADDRESS,
SERVICE_MIXED_INBOUND_PORT,
+103 -4
View File
@@ -405,6 +405,7 @@ function tproxy_inbound_matcher() {
}
function base_config(settings, service_address, runtime_context) {
runtime_context = object_or_empty(runtime_context);
let log_level = option(settings, "log_level", "warn");
let rewrite_ttl = int_option(settings, "dns_rewrite_ttl", "60");
let turbo_cache = bool_option(settings, "dns_turbo_cache", false);
@@ -494,6 +495,14 @@ function base_config(settings, service_address, runtime_context) {
push(inbounds, { type: "tproxy", tag: runtime_constants.TPROXY_INBOUND6_TAG, listen: runtime_constants.TPROXY_INBOUND6_ADDRESS, listen_port: runtime_constants.TPROXY_INBOUND_PORT, tcp_fast_open: true, udp_fragment: true });
}
push(inbounds, { type: "direct", tag: runtime_constants.DNS_INBOUND_TAG, listen: runtime_constants.DNS_INBOUND_ADDRESS, listen_port: runtime_constants.DNS_INBOUND_PORT });
if (runtime_context.source_aware_dns) {
push(inbounds, {
type: "direct",
tag: runtime_constants.SOURCE_DNS_INBOUND_TAG,
listen: core_ip.ipv6_supported() ? runtime_constants.SOURCE_DNS_INBOUND_ADDRESS : "0.0.0.0",
listen_port: runtime_constants.SOURCE_DNS_INBOUND_PORT
});
}
for (let inbound in dns_config.inbounds)
push(inbounds, inbound);
@@ -502,7 +511,6 @@ function base_config(settings, service_address, runtime_context) {
{ type: "direct", tag: runtime_constants.BYPASS_OUTBOUND_TAG }
];
runtime_context = object_or_empty(runtime_context);
runtime_context.dns_health_inbounds = dns_config.sniff_inbounds;
runtime_context.default_domain_resolver = runtime_dns.default_domain_resolver(settings);
@@ -539,6 +547,14 @@ 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 route_section = runtime_route.config(settings, runtime_context);
if (is_sb_1_14_plus)
route_section.default_http_client = "ruleset-http";
let cache_file_section = {
enabled: true,
path: cache_path,
@@ -549,7 +565,7 @@ function base_config(settings, service_address, runtime_context) {
else
cache_file_section.store_dns = true;
return {
let base_cfg = {
log: {
disabled: false,
level: log_level,
@@ -561,7 +577,7 @@ function base_config(settings, service_address, runtime_context) {
endpoints: [],
inbounds,
outbounds: default_outbounds,
route: runtime_route.config(settings, runtime_context),
route: route_section,
services: [],
experimental: {
cache_file: cache_file_section,
@@ -569,6 +585,11 @@ function base_config(settings, service_address, runtime_context) {
},
__dns_hosts_predefined: dns_hosts_predefined
};
if (is_sb_1_14_plus)
base_cfg.http_clients = [{ tag: "ruleset-http" }];
return base_cfg;
}
@@ -991,6 +1012,78 @@ function add_content_blocking(config) {
add_content_block_route_rules(config, profiles);
}
function single_or_array(values) {
if (type(values) != "array")
return values;
if (length(values) == 1)
return values[0];
return values;
}
function source_aware_dns_sources(sections) {
let sources = [];
let seen = {};
let add_source = function(value) {
value = as_string(value);
if (value == "" || seen[value])
return;
seen[value] = true;
push(sources, value);
};
for (let section in sections) {
let action = option(section, "action", "");
let has_dns_matchers = connections.has_dns_matchers(section);
if (has_dns_matchers) {
for (let ip in list_option(section, "source_ip_cidr"))
add_source(ip);
}
if (action == "bypass") {
for (let ip in list_option(section, "fully_routed_ips"))
add_source(ip);
}
if (action == "dns") {
for (let ip in list_option(section, "fully_routed_ips"))
add_source(ip);
for (let ip in list_option(section, "source_ip_cidr"))
add_source(ip);
}
}
return sources;
}
function add_source_aware_dns_support(config, source_aware_dns) {
if (length(source_aware_dns) == 0)
return;
push(config.dns.servers, {
type: "udp",
tag: runtime_constants.DNSMASQ_DNS_SERVER_TAG,
server: "127.0.0.1",
server_port: 53
});
}
function add_source_aware_dns_fallback(config, source_aware_dns) {
if (length(source_aware_dns) == 0)
return;
let settings = runtime_settings_cache || {};
let rewrite_ttl = int_option(settings, "dns_rewrite_ttl", "60");
push(config.dns.rules, {
action: "route",
server: runtime_constants.DNSMASQ_DNS_SERVER_TAG,
inbound: [ runtime_constants.SOURCE_DNS_INBOUND_TAG ],
source_ip_cidr: single_or_array(source_aware_dns),
rewrite_ttl
});
}
function generate_config(output_path, service_address, mwan3_active, supports_xhttp) {
ctx.runtime_ruleset_folder = runtime_ruleset_folder;
runtime_supports_xhttp = supports_xhttp == null || as_string(supports_xhttp) == ""
@@ -1007,7 +1100,12 @@ function generate_config(output_path, service_address, mwan3_active, supports_xh
if (length(sections) == 0 && length(servers) == 0)
runtime_generate_unsupported("no enabled sections");
let config = base_config(settings, service_address, { mwan3_active: cli_bool(mwan3_active) });
let source_aware_dns = source_aware_dns_sources(sections);
let config = base_config(settings, service_address, {
mwan3_active: cli_bool(mwan3_active),
source_aware_dns: length(source_aware_dns) > 0
});
add_source_aware_dns_support(config, source_aware_dns);
let taken = reserved_runtime_tag_set(config.outbounds);
reserve_section_outbound_tags(sections, taken);
for (let server in servers)
@@ -1018,6 +1116,7 @@ function generate_config(output_path, service_address, mwan3_active, supports_xh
for (let section in sections)
add_route_for_section(config, section);
add_server_routes(config, servers, sections);
add_source_aware_dns_fallback(config, source_aware_dns);
// Append dns_hosts predefined rules AFTER section DNS rules so that
// FakeIP/section-level DNS routing takes precedence over hardcoded IPs
@@ -1296,8 +1296,23 @@ function add_awg_endpoint(config, section) {
let mtu = int_option(section, "awg_mtu", "1280");
endpoint.mtu = mtu > 0 ? mtu : 1280;
let keepalive = int_option(section, "awg_keepalive", "25");
endpoint.peers[0].persistent_keepalive_interval = keepalive > 0 ? keepalive : 25;
let sb_variant_file = getenv("SB_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant";
let sb_version_file = getenv("SB_VERSION_STATE_FILE") || "/etc/tachyon/sing-box-version";
let sb_version_val = trim(fs.readfile(sb_version_file) || "");
let sb_variant_val = trim(fs.readfile(sb_variant_file) || "");
let is_lx = sb_variant_val == "lx" || index(sb_version_val, "-lx") >= 0;
let keepalive = int_or_range_option(section, "awg_keepalive", 25);
if (is_lx) {
endpoint.peers[0].persistent_keepalive_interval = keepalive;
} else {
if (type(keepalive) == "string") {
let m = match(keepalive, /^([0-9]+)/);
endpoint.peers[0].persistent_keepalive_interval = m ? int(m[1], 10) : 25;
} else {
endpoint.peers[0].persistent_keepalive_interval = keepalive > 0 ? keepalive : 25;
}
}
let jc = int_option(section, "awg_jc", "4");
if (jc > 10) jc = 10;
@@ -1331,10 +1346,6 @@ function add_awg_endpoint(config, section) {
let j2 = awg_cps_option(section, "awg_j2");
let j3 = awg_cps_option(section, "awg_j3");
let itime = int_option(section, "awg_itime", "0");
let sb_variant_file = getenv("SB_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant";
let sb_version_file = getenv("SB_VERSION_STATE_FILE") || "/etc/tachyon/sing-box-version";
let sb_version_val = trim(fs.readfile(sb_version_file) || "");
let is_lx = sb_version_val != "" ? (index(sb_version_val, "-lx") >= 0) : (trim(fs.readfile(sb_variant_file) || "") == "lx");
if (is_lx) {
// sing-box-lx expects AWG fields at the endpoint root (AWG 2.0 schema).
@@ -1382,7 +1393,8 @@ function add_awg_endpoint(config, section) {
if (awg_ver == "") {
if (option(section, "awg_rekey_after_time", "") != "" || option(section, "awg_rekey_timeout", "") != "" ||
option(section, "awg_reject_after_time", "") != "" || option(section, "awg_keepalive_timeout", "") != "" ||
option(section, "awg_max_handshake_attempts", "") != "") {
option(section, "awg_max_handshake_attempts", "") != "" ||
bool_option(section, "awg_random_trailers", false) || bool_option(section, "awg_disable_cookies", false)) {
awg_ver = "3.1";
} else if (option(section, "awg_header_protection_key", "") != "" || option(section, "awg_content_padding_addition", "") != "") {
awg_ver = "3.0";
@@ -1446,6 +1458,13 @@ function add_awg_endpoint(config, section) {
if (is_lx) endpoint.max_handshake_attempts = mha;
else endpoint.amnezia.max_handshake_attempts = mha;
}
if (is_lx) {
if (bool_option(section, "awg_random_trailers", false))
endpoint.random_trailers = true;
if (bool_option(section, "awg_disable_cookies", false))
endpoint.disable_cookies = true;
}
}
let detour = option(section, "awg_detour", "");
@@ -1047,6 +1047,18 @@ function add_dns_server_for_section(config, section) {
return server_tags;
}
function source_dns_inbound_matcher() {
return [ runtime_constants.SOURCE_DNS_INBOUND_TAG ];
}
function add_source_dns_matchers(rule, source_ip_cidr) {
if (length(source_ip_cidr) == 0)
return;
rule.inbound = source_dns_inbound_matcher();
rule.source_ip_cidr = single_or_array(source_ip_cidr);
}
function add_dns_action_rules_for_section(config, section) {
let domains = domain_conditions(section);
let domain = domains.domain;
@@ -1055,6 +1067,8 @@ function add_dns_action_rules_for_section(config, section) {
let domain_regex = domains.domain_regex;
let rule_set_tags = [];
let section_name = section[".name"];
let source_ip_cidr = legacy_condition_values(section, "source_ip_cidr");
let fully_routed_ips = list_option(section, "fully_routed_ips");
for (let community in connections.community_lists(section)) {
let ensured = ensure_community_ruleset(config, section_name, as_string(community));
@@ -1087,6 +1101,15 @@ function add_dns_action_rules_for_section(config, section) {
let has_inline_domains = length(domain) > 0 || length(domain_suffix) > 0 ||
length(domain_keyword) > 0 || length(domain_regex) > 0;
if (length(fully_routed_ips) > 0) {
let dns_rule = {
action: "route",
server: server_tag,
rewrite_ttl
};
add_source_dns_matchers(dns_rule, fully_routed_ips);
push_dns_matcher_rule(config, dns_rule);
}
if (has_inline_domains) {
let dns_rule = {
action: "route",
@@ -1097,17 +1120,20 @@ function add_dns_action_rules_for_section(config, section) {
add_domain_array(dns_rule, "domain_suffix", domain_suffix);
add_domain_array(dns_rule, "domain_keyword", domain_keyword);
add_domain_array(dns_rule, "domain_regex", domain_regex);
add_source_dns_matchers(dns_rule, source_ip_cidr);
push_dns_matcher_rule(config, dns_rule);
}
if (length(rule_set_tags) > 0) {
push_dns_matcher_rule(config, {
let dns_rule = {
action: "route",
server: server_tag,
rewrite_ttl,
rule_set: single_or_array(rule_set_tags)
});
};
add_source_dns_matchers(dns_rule, source_ip_cidr);
push_dns_matcher_rule(config, dns_rule);
}
if (!has_inline_domains && length(rule_set_tags) == 0)
if (!has_inline_domains && length(rule_set_tags) == 0 && length(fully_routed_ips) == 0)
ctx.runtime_generate_unsupported("DNS action '" + section_name + "' has no domain matchers");
}
@@ -1446,15 +1472,18 @@ function add_combined_route_for_section(config, section) {
add_domain_array(dns_rule, "domain_suffix", domain_suffix);
add_domain_array(dns_rule, "domain_keyword", domain_keyword);
add_domain_array(dns_rule, "domain_regex", domain_regex);
add_source_dns_matchers(dns_rule, source_ip_cidr);
push_dns_matcher_rule(config, dns_rule);
}
if (length(dns_rule_set_tags) > 0) {
push_dns_matcher_rule(config, {
let dns_rule = {
action: "route",
server: section_dns_server(section),
rewrite_ttl,
rule_set: single_or_array(dns_rule_set_tags)
});
};
add_source_dns_matchers(dns_rule, source_ip_cidr);
push_dns_matcher_rule(config, dns_rule);
}
}
+10 -5
View File
@@ -65,15 +65,20 @@ function config(settings, runtime) {
let output_network_interface = option(settings, "output_network_interface", "");
let mwan3_active = type(runtime) == "object" && bool_value(runtime.mwan3_active);
let sniff_inbounds = [ runtime_constants.TPROXY_INBOUND_TAG, runtime_constants.DNS_INBOUND_TAG ];
if (type(runtime) == "object" && bool_value(runtime.source_aware_dns))
push(sniff_inbounds, runtime_constants.SOURCE_DNS_INBOUND_TAG);
if (type(runtime) == "object" && type(runtime.dns_health_inbounds) == "array")
for (let inbound in runtime.dns_health_inbounds)
push(sniff_inbounds, inbound);
let rules = [
{ action: "sniff", inbound: sniff_inbounds },
{ action: "hijack-dns", port: 53 },
{ action: "hijack-dns", protocol: "dns" }
];
if (type(runtime) == "object" && bool_value(runtime.source_aware_dns))
push(rules, { action: "hijack-dns", inbound: [ runtime_constants.SOURCE_DNS_INBOUND_TAG ] });
let result = {
rules: [
{ action: "sniff", inbound: sniff_inbounds },
{ action: "hijack-dns", port: 53 },
{ action: "hijack-dns", protocol: "dns" }
],
rules,
rule_set: [],
final: runtime_constants.DIRECT_OUTBOUND_TAG,
auto_detect_interface: output_network_interface == "" && !mwan3_active,
+9 -2
View File
@@ -339,14 +339,16 @@ function add_standard_inbound(config, section, protocol, tag_name) {
let sb_variant_file = getenv("SB_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant";
let sb_version_file = getenv("SB_VERSION_STATE_FILE") || "/etc/tachyon/sing-box-version";
let sb_version_val = trim(fs.readfile(sb_version_file) || "");
let is_lx = sb_version_val != "" ? (index(sb_version_val, "-lx") >= 0) : (trim(fs.readfile(sb_variant_file) || "") == "lx");
let sb_variant_val = trim(fs.readfile(sb_variant_file) || "");
let is_lx = sb_variant_val == "lx" || index(sb_version_val, "-lx") >= 0;
// AmneziaWG version handling: 2.0, 3.0, and 3.1 (sing-box-lx v1.14.0-lx.32+)
let awg_ver = option(section, "awg_version", "");
if (awg_ver == "") {
if (option(section, "awg_rekey_after_time", "") != "" || option(section, "awg_rekey_timeout", "") != "" ||
option(section, "awg_reject_after_time", "") != "" || option(section, "awg_keepalive_timeout", "") != "" ||
option(section, "awg_max_handshake_attempts", "") != "") {
option(section, "awg_max_handshake_attempts", "") != "" ||
bool_option(section, "awg_random_trailers", false) || bool_option(section, "awg_disable_cookies", false)) {
awg_ver = "3.1";
} else if (option(section, "awg_header_protection_key", "") != "" || option(section, "awg_content_padding_addition", "") != "") {
awg_ver = "3.0";
@@ -408,6 +410,11 @@ function add_standard_inbound(config, section, protocol, tag_name) {
let mha = option(section, "awg_max_handshake_attempts", "");
if (mha != "") inbound.max_handshake_attempts = mha;
if (bool_option(section, "awg_random_trailers", false))
inbound.random_trailers = true;
if (bool_option(section, "awg_disable_cookies", false))
inbound.disable_cookies = true;
}
} else {
if (i1 != "") amnezia.i1 = i1;