feat: поддержка AmneziaWG 3.1, keepalive ranges, безопасный ruleset HTTP client и Source-Aware DNS
This commit is contained in:
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user