Add DNS action for routing sections
This commit is contained in:
+388
-356
File diff suppressed because it is too large
Load Diff
+376
-353
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: FORKOP\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-12 20:23+0300\n"
|
||||
"PO-Revision-Date: 2026-07-12 20:23+0300\n"
|
||||
"POT-Creation-Date: 2026-07-12 00:20+0300\n"
|
||||
"PO-Revision-Date: 2026-07-12 00:20+0300\n"
|
||||
"Last-Translator: ushan0v\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: ru\n"
|
||||
@@ -71,11 +71,11 @@ msgstr "Добавьте хотя бы один DNS-сервер"
|
||||
msgid "Add prefix to nodes"
|
||||
msgstr "Добавлять префикс к узлам"
|
||||
|
||||
msgid "Add URLs or local paths to .lst lists"
|
||||
msgstr "Добавляйте URL или локальные пути к спискам .lst"
|
||||
msgid "Add URLs or local paths to .lst lists. Only domain rules are supported."
|
||||
msgstr "Добавьте URL или локальные пути к спискам .lst. Поддерживаются только доменные правила."
|
||||
|
||||
msgid "Add URLs or local paths to .srs / .json lists"
|
||||
msgstr "Добавьте URL или локальные пути к спискам .srs / .json"
|
||||
msgid "Add URLs or local paths to .srs / .json lists. Only domain rules are supported."
|
||||
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Поддерживаются только доменные правила."
|
||||
|
||||
msgid "Additional marking rules found"
|
||||
msgstr "Найдены дополнительные правила маркировки"
|
||||
@@ -467,6 +467,9 @@ msgstr "Стратегия DNS"
|
||||
msgid "DNS through proxy"
|
||||
msgstr "DNS через прокси"
|
||||
|
||||
msgid "DNS through section"
|
||||
msgstr "DNS через секцию"
|
||||
|
||||
msgid "DNS Unavailability Timeout"
|
||||
msgstr "Таймаут недоступности DNS"
|
||||
|
||||
@@ -485,8 +488,8 @@ msgstr "Не тестировать указанные серверы."
|
||||
msgid "Does not resolve"
|
||||
msgstr "Не резолвится"
|
||||
|
||||
msgid "Domain and IP Lists"
|
||||
msgstr "Списки доменов и IPs"
|
||||
msgid "Domain lists"
|
||||
msgstr "Списки доменов"
|
||||
|
||||
msgid "Domain Resolver"
|
||||
msgstr "Резолвер доменов"
|
||||
@@ -587,6 +590,9 @@ msgstr "Введите отображаемое имя"
|
||||
msgid "Enter a level name"
|
||||
msgstr "Введите название уровня"
|
||||
|
||||
msgid "Enter a valid DNS server address"
|
||||
msgstr "Укажите корректный адрес DNS-сервера"
|
||||
|
||||
msgid "Enter HWID"
|
||||
msgstr "Укажите HWID"
|
||||
|
||||
@@ -1505,6 +1511,9 @@ msgstr "Маршрут"
|
||||
msgid "Route main DNS requests through the selected section."
|
||||
msgstr "Маршрутизировать основные DNS-запросы через выбранную секцию."
|
||||
|
||||
msgid "Route requests to this DNS server through another section."
|
||||
msgstr "Направлять запросы к этому DNS-серверу через другую секцию."
|
||||
|
||||
msgid "Routing mode"
|
||||
msgstr "Режим маршрутизации"
|
||||
|
||||
@@ -1580,8 +1589,8 @@ msgstr "Выберите распространённый профиль кли
|
||||
msgid "Select a network interface"
|
||||
msgstr "Выберите сетевой интерфейс"
|
||||
|
||||
msgid "Select a predefined list for routing"
|
||||
msgstr "Выберите предустановленный набор правил для маршрутизации"
|
||||
msgid "Select a predefined domain list"
|
||||
msgstr "Выберите готовый список доменов"
|
||||
|
||||
msgid "Select a section"
|
||||
msgstr "Выберите секцию"
|
||||
|
||||
@@ -576,7 +576,12 @@ function rule_has_list_update_source(section) {
|
||||
if (rule_config == null)
|
||||
rule_config = require("config.rule");
|
||||
|
||||
return bool_option(section, "enabled", true) && (
|
||||
if (!bool_option(section, "enabled", true))
|
||||
return false;
|
||||
if (option(section, "action", "") == "dns")
|
||||
return list_has_remote_references(option(section, "domain_ip_lists", ""));
|
||||
|
||||
return (
|
||||
rule_config.has_community_subnet_list(connections.community_lists_value(section)) ||
|
||||
option(section, "remote_domain_lists", "") != "" ||
|
||||
option(section, "remote_subnet_lists", "") != "" ||
|
||||
@@ -2051,11 +2056,12 @@ function import_domain_ip_list_file_into_rulesets(filepath, section) {
|
||||
|
||||
let ruleset_filepath = domain_ip_list_ruleset_path(section);
|
||||
let ok = nft_module_success([ "split-domain-subnet-file", filepath, domains_tmpfile, subnets_tmpfile ]);
|
||||
let domains_only = option(section, "action", "") == "dns";
|
||||
if (ok)
|
||||
ok = ruleset_module_success([ "import-plain-list", domains_tmpfile, ruleset_filepath, "domain_suffix", "domains", "5000" ]);
|
||||
if (ok)
|
||||
if (ok && !domains_only)
|
||||
ok = ruleset_module_success([ "import-plain-list", subnets_tmpfile, ruleset_filepath, "ip_cidr", "subnets", "5000" ]);
|
||||
if (ok)
|
||||
if (ok && !domains_only)
|
||||
ok = add_plain_subnet_file_to_nft_for_section(section, subnets_tmpfile);
|
||||
|
||||
remove_files([ domains_tmpfile, subnets_tmpfile ]);
|
||||
@@ -2107,6 +2113,8 @@ function rebuild_domain_ip_lists_from_rule(section, settings) {
|
||||
function import_builtin_subnets_from_rule(section, settings) {
|
||||
if (!bool_option(section, "enabled", true))
|
||||
return true;
|
||||
if (option(section, "action", "") == "dns")
|
||||
return true;
|
||||
|
||||
let ok = true;
|
||||
for (let service in connections.community_lists(section)) {
|
||||
@@ -2218,6 +2226,8 @@ function import_custom_ruleset_subnets_from_remote(url, format, section, label,
|
||||
function import_rule_sets_with_subnets_from_rule(section, settings) {
|
||||
if (!bool_option(section, "enabled", true))
|
||||
return true;
|
||||
if (option(section, "action", "") == "dns")
|
||||
return true;
|
||||
|
||||
let references = connections.rule_sets_with_subnets(section);
|
||||
if (length(references) == 0)
|
||||
|
||||
@@ -814,7 +814,7 @@ function rule_action(section) {
|
||||
}
|
||||
|
||||
function rule_action_supported(action) {
|
||||
return contains([ "connection", "proxy", "outbound", "vpn", "bypass", "block", "zapret", "zapret2", "byedpi" ], as_string(action));
|
||||
return contains([ "connection", "proxy", "outbound", "vpn", "bypass", "block", "dns", "zapret", "zapret2", "byedpi" ], as_string(action));
|
||||
}
|
||||
|
||||
function server_routing_section_action_supported(action) {
|
||||
@@ -1274,7 +1274,52 @@ function validate_provider_strategy(kind, section, context) {
|
||||
fail_validation("Invalid ByeDPI strategy for rule '" + name + "': " + result.message);
|
||||
}
|
||||
|
||||
function validate_rule(section, context) {
|
||||
function dns_action_has_domain_matchers(section) {
|
||||
for (let key in [ "domain", "domain_suffix", "domain_keyword", "domain_regex" ])
|
||||
if (option(section, key, "") != "" || option(section, key + "_text", "") != "" || length(list_option(section, key)) > 0)
|
||||
return true;
|
||||
|
||||
return length(connections.community_lists(section)) > 0 ||
|
||||
length(connections.rule_sets(section)) > 0 ||
|
||||
length(list_option(section, "domain_ip_lists")) > 0;
|
||||
}
|
||||
|
||||
function validate_dns_action(section, sections, context) {
|
||||
let name = section_name(section);
|
||||
let dns_type = option(section, "dns_type", "udp");
|
||||
if (!contains([ "udp", "dot", "doh" ], dns_type))
|
||||
fail_validation("DNS rule '" + name + "' uses unsupported protocol '" + dns_type + "'. Use udp, dot, or doh. Aborted.");
|
||||
let dns_server = option(section, "dns_server", "");
|
||||
if (!dns_server_value_valid(dns_server))
|
||||
fail_validation("DNS rule '" + name + "' has an invalid DNS server '" + dns_server + "'. Aborted.");
|
||||
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.");
|
||||
if (!bool_option(section, "dns_detour_enabled", false))
|
||||
return;
|
||||
|
||||
let target_name = option(section, "dns_detour_section", "");
|
||||
if (target_name == "")
|
||||
fail_validation("DNS rule '" + name + "' routes DNS through a section, but no section is selected. Aborted.");
|
||||
if (target_name == name)
|
||||
fail_validation("DNS rule '" + name + "' cannot use itself as a DNS detour. Aborted.");
|
||||
|
||||
let target = null;
|
||||
for (let candidate in sections)
|
||||
if (section_name(candidate) == target_name) {
|
||||
target = candidate;
|
||||
break;
|
||||
}
|
||||
if (target == null)
|
||||
fail_validation("DNS rule '" + name + "' references missing detour section '" + target_name + "'. Aborted.");
|
||||
if (!section_enabled(target))
|
||||
fail_validation("DNS rule '" + name + "' references disabled detour section '" + target_name + "'. Aborted.");
|
||||
if (!download_section_action_available(rule_action(target), context.byedpi_installed, context.zapret_installed, context.zapret2_installed))
|
||||
fail_validation("DNS rule '" + name + "' references detour section '" + target_name + "' with unsupported action '" + rule_action(target) + "'. Select an enabled Connection, VPN, Zapret, Zapret2, or ByeDPI section. Aborted.");
|
||||
}
|
||||
|
||||
function validate_rule(section, sections, context) {
|
||||
if (!section_enabled(section))
|
||||
return;
|
||||
|
||||
@@ -1285,8 +1330,12 @@ function validate_rule(section, context) {
|
||||
if (!rule_action_supported(action))
|
||||
fail_validation("Enabled rule '" + name + "' uses unsupported action '" + action + "'. Aborted.");
|
||||
|
||||
for (let value in list_option(section, "ports"))
|
||||
validate_port_condition_value(value, name);
|
||||
if (action != "dns")
|
||||
for (let value in list_option(section, "ports"))
|
||||
validate_port_condition_value(value, name);
|
||||
|
||||
if (action == "dns")
|
||||
validate_dns_action(section, sections, context);
|
||||
|
||||
if (action == "zapret") {
|
||||
if (!context.zapret_installed) {
|
||||
@@ -1572,7 +1621,7 @@ function validate_runtime_config(context) {
|
||||
validate_server_routing_sections(sections);
|
||||
|
||||
for (let section in sections)
|
||||
validate_rule(section, context);
|
||||
validate_rule(section, sections, context);
|
||||
}
|
||||
|
||||
function context_from_runtime() {
|
||||
|
||||
@@ -1470,7 +1470,10 @@ function nft_rule_signature_body(body, section) {
|
||||
if (section_name == "" || !bool_option(section, "enabled", true))
|
||||
return body;
|
||||
|
||||
body = signature_add_value(body, "rule." + section_name + ".action", option(section, "action", ""));
|
||||
let action = option(section, "action", "");
|
||||
body = signature_add_value(body, "rule." + section_name + ".action", action);
|
||||
if (action == "dns")
|
||||
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 + ".ports", section_rule_ports_csv(section));
|
||||
@@ -1612,6 +1615,8 @@ function nft_add_section_source_matchers(section, table, chunk_size_text) {
|
||||
function 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) {
|
||||
if (!bool_option(section, "enabled", true))
|
||||
return true;
|
||||
if (section_action(section) == "dns")
|
||||
return true;
|
||||
|
||||
let ports = section_rule_ports_csv(section);
|
||||
let ip_values = section_rule_condition_csv(section, "ip_cidr", "subnets");
|
||||
|
||||
@@ -664,8 +664,13 @@ function has_community_subnet_list(value) {
|
||||
return rule_config.has_community_subnet_list(value);
|
||||
}
|
||||
|
||||
function rule_has_list_update_source(enabled, community_lists, remote_domain_lists, remote_subnet_lists, rule_set_with_subnets, domain_ip_lists) {
|
||||
return arg_bool(enabled) && (
|
||||
function rule_has_list_update_source(enabled, action, community_lists, remote_domain_lists, remote_subnet_lists, rule_set_with_subnets, domain_ip_lists) {
|
||||
if (!arg_bool(enabled))
|
||||
return false;
|
||||
if (as_string(action) == "dns")
|
||||
return list_has_remote_references(domain_ip_lists);
|
||||
|
||||
return (
|
||||
has_community_subnet_list(community_lists) ||
|
||||
as_string(remote_domain_lists) != "" ||
|
||||
as_string(remote_subnet_lists) != "" ||
|
||||
@@ -674,8 +679,11 @@ function rule_has_list_update_source(enabled, community_lists, remote_domain_lis
|
||||
);
|
||||
}
|
||||
|
||||
function rule_has_nft_list_update_source(enabled, community_lists, remote_subnet_lists, rule_set_with_subnets, domain_ip_lists) {
|
||||
return arg_bool(enabled) && (
|
||||
function rule_has_nft_list_update_source(enabled, action, community_lists, remote_subnet_lists, rule_set_with_subnets, domain_ip_lists) {
|
||||
if (!arg_bool(enabled) || as_string(action) == "dns")
|
||||
return false;
|
||||
|
||||
return (
|
||||
has_community_subnet_list(community_lists) ||
|
||||
as_string(remote_subnet_lists) != "" ||
|
||||
as_string(rule_set_with_subnets) != "" ||
|
||||
@@ -873,7 +881,10 @@ function nft_runtime_signature_body(settings, sections) {
|
||||
if (name == "" || !bool_option(section, "enabled", true))
|
||||
continue;
|
||||
|
||||
body = signature_add_value(body, "rule." + name + ".action", option(section, "action", ""));
|
||||
let action = option(section, "action", "");
|
||||
body = signature_add_value(body, "rule." + name + ".action", action);
|
||||
if (action == "dns")
|
||||
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 + ".ports", section_rule_ports_csv(section));
|
||||
@@ -1046,6 +1057,13 @@ function append_list_update_signature_body(body, section) {
|
||||
if (name == "" || !bool_option(section, "enabled", true))
|
||||
return body;
|
||||
|
||||
let action = option(section, "action", "");
|
||||
body = signature_add_value(body, "lists." + name + ".action", action);
|
||||
if (action == "dns") {
|
||||
body = signature_add_value(body, "lists." + name + ".domain_ip_lists", option(section, "domain_ip_lists", ""));
|
||||
return body;
|
||||
}
|
||||
|
||||
body = signature_add_value(body, "lists." + name + ".ports", section_rule_ports_csv(section));
|
||||
body = signature_add_value(body, "lists." + name + ".community_subnet_lists", rule_config.filter_community_subnet_lists_value(connections.community_lists_value(section)));
|
||||
body = signature_add_value(body, "lists." + name + ".remote_domain_lists", option(section, "remote_domain_lists", ""));
|
||||
@@ -1212,18 +1230,28 @@ function append_sing_box_rule_signature_body(body, section, sections) {
|
||||
else if (action == "zapret" || action == "zapret2") {
|
||||
body = signature_add_mixed_proxy_body(body, section, prefix);
|
||||
}
|
||||
else if (action == "dns") {
|
||||
body = signature_add_value(body, prefix + ".dns_type", option(section, "dns_type", "udp"));
|
||||
body = signature_add_value(body, prefix + ".dns_server", option(section, "dns_server", ""));
|
||||
body = signature_add_value(body, prefix + ".dns_detour_enabled", bool_option_value(section, "dns_detour_enabled", false));
|
||||
if (bool_option(section, "dns_detour_enabled", false))
|
||||
body = signature_add_value(body, prefix + ".dns_detour_section", option(section, "dns_detour_section", ""));
|
||||
}
|
||||
|
||||
body = signature_add_value(body, prefix + ".domain", section_rule_condition_csv(section, "domain", "domains"));
|
||||
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"));
|
||||
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 + ".ports", section_rule_ports_csv(section));
|
||||
body = signature_add_value(body, prefix + ".fully_routed_ips", option(section, "fully_routed_ips", ""));
|
||||
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 + ".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 + ".community_lists", connections.community_lists_value(section));
|
||||
body = signature_add_value(body, prefix + ".rule_set", connections.rule_sets_value(section));
|
||||
body = signature_add_value(body, prefix + ".rule_set_with_subnets", connections.rule_sets_with_subnets_value(section));
|
||||
if (action != "dns")
|
||||
body = signature_add_value(body, prefix + ".rule_set_with_subnets", connections.rule_sets_with_subnets_value(section));
|
||||
body = signature_add_value(body, prefix + ".domain_ip_lists", option(section, "domain_ip_lists", ""));
|
||||
|
||||
return body;
|
||||
@@ -1555,6 +1583,7 @@ function has_list_update_sources_from_sections(sections) {
|
||||
for (let section in sections)
|
||||
if (rule_has_list_update_source(
|
||||
bool_option(section, "enabled", true),
|
||||
option(section, "action", ""),
|
||||
connections.community_lists_value(section),
|
||||
option(section, "remote_domain_lists", ""),
|
||||
option(section, "remote_subnet_lists", ""),
|
||||
@@ -1570,6 +1599,7 @@ function has_nft_list_update_sources_from_sections(sections) {
|
||||
for (let section in sections)
|
||||
if (rule_has_nft_list_update_source(
|
||||
bool_option(section, "enabled", true),
|
||||
option(section, "action", ""),
|
||||
connections.community_lists_value(section),
|
||||
option(section, "remote_subnet_lists", ""),
|
||||
connections.rule_sets_with_subnets_value(section),
|
||||
@@ -1783,9 +1813,9 @@ else if (mode == "community-service-has-subnet-list")
|
||||
else if (mode == "filter-community-subnet-lists")
|
||||
filter_community_subnet_lists(ARGV[1]);
|
||||
else if (mode == "rule-has-list-update-source")
|
||||
exit(rule_has_list_update_source(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]) ? 0 : 1);
|
||||
exit(rule_has_list_update_source(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7]) ? 0 : 1);
|
||||
else if (mode == "rule-has-nft-list-update-source")
|
||||
exit(rule_has_nft_list_update_source(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]) ? 0 : 1);
|
||||
exit(rule_has_nft_list_update_source(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]) ? 0 : 1);
|
||||
else if (mode == "rule-has-subscription-update-source")
|
||||
exit(rule_has_subscription_update_source(ARGV[1], ARGV[2]) ? 0 : 1);
|
||||
else if (mode == "service-trigger-signature")
|
||||
|
||||
@@ -2255,7 +2255,7 @@ function source_file_exists(path) {
|
||||
return fs.readfile(path) != null;
|
||||
}
|
||||
|
||||
function rebuild_local_domain_ip_list_ruleset(section_name, references) {
|
||||
function rebuild_local_domain_ip_list_ruleset(section_name, references, domains_only) {
|
||||
let ruleset_path = domain_ip_list_ruleset_path(section_name);
|
||||
let has_local = false;
|
||||
|
||||
@@ -2282,15 +2282,16 @@ function rebuild_local_domain_ip_list_ruleset(section_name, references) {
|
||||
}
|
||||
|
||||
source_rulesets.import_plain_list(reference, ruleset_path, "domain_suffix", "domains", "5000");
|
||||
source_rulesets.import_plain_list(reference, ruleset_path, "ip_cidr", "subnets", "5000");
|
||||
if (!domains_only)
|
||||
source_rulesets.import_plain_list(reference, ruleset_path, "ip_cidr", "subnets", "5000");
|
||||
}
|
||||
}
|
||||
|
||||
function add_domain_ip_list_ruleset(config, section_name, rule_set_tags, dns_rule_set_tags, references) {
|
||||
function add_domain_ip_list_ruleset(config, section_name, rule_set_tags, dns_rule_set_tags, references, domains_only) {
|
||||
if (length(references) == 0)
|
||||
return;
|
||||
|
||||
rebuild_local_domain_ip_list_ruleset(section_name, references);
|
||||
rebuild_local_domain_ip_list_ruleset(section_name, references, domains_only);
|
||||
|
||||
let ruleset_path = domain_ip_list_ruleset_path(section_name);
|
||||
if (!source_rulesets.has_rules(ruleset_path))
|
||||
@@ -2306,7 +2307,8 @@ function add_domain_ip_list_ruleset(config, section_name, rule_set_tags, dns_rul
|
||||
});
|
||||
}
|
||||
|
||||
push(rule_set_tags, tag_name);
|
||||
if (!domains_only)
|
||||
push(rule_set_tags, tag_name);
|
||||
if (source_rulesets.has_domain_matchers(ruleset_path))
|
||||
push(dns_rule_set_tags, tag_name);
|
||||
}
|
||||
@@ -2410,6 +2412,84 @@ function single_or_array(values) {
|
||||
return length(values) == 1 ? values[0] : values;
|
||||
}
|
||||
|
||||
function dns_action_server_tag(section_name) {
|
||||
return runtime_constants.tag(section_name, "dns-server");
|
||||
}
|
||||
|
||||
function dns_action_detour_tag(section) {
|
||||
if (!bool_option(section, "dns_detour_enabled", false))
|
||||
return "";
|
||||
let target_section = option(section, "dns_detour_section", "");
|
||||
return target_section == "" ? "" : outbound_tag(target_section);
|
||||
}
|
||||
|
||||
function add_dns_server_for_section(config, section) {
|
||||
let server = runtime_dns.server_from_options(
|
||||
dns_action_server_tag(section[".name"]),
|
||||
option(section, "dns_type", "udp"),
|
||||
option(section, "dns_server", ""),
|
||||
dns_action_detour_tag(section)
|
||||
);
|
||||
if (server.unsupported)
|
||||
runtime_generate_unsupported(server.unsupported);
|
||||
push(config.dns.servers, server);
|
||||
}
|
||||
|
||||
function add_dns_action_rules_for_section(config, section) {
|
||||
let domain = domain_condition_values(section, "domain");
|
||||
let domain_suffix = domain_condition_values(section, "domain_suffix");
|
||||
let domain_keyword = domain_condition_values(section, "domain_keyword");
|
||||
let domain_regex = domain_condition_values(section, "domain_regex");
|
||||
let rule_set_tags = [];
|
||||
let section_name = section[".name"];
|
||||
|
||||
for (let community in connections.community_lists(section)) {
|
||||
let ensured = ensure_community_ruleset(config, section_name, as_string(community));
|
||||
push(rule_set_tags, ensured.tag);
|
||||
}
|
||||
for (let reference in connections.rule_sets(section)) {
|
||||
let ensured = ensure_custom_ruleset(config, as_string(reference));
|
||||
if (ensured.kind == "domains")
|
||||
push(rule_set_tags, ensured.tag);
|
||||
}
|
||||
add_domain_ip_list_ruleset(
|
||||
config,
|
||||
section_name,
|
||||
[],
|
||||
rule_set_tags,
|
||||
list_option(section, "domain_ip_lists"),
|
||||
true
|
||||
);
|
||||
|
||||
let rewrite_ttl = int_option(runtime_settings(), "dns_rewrite_ttl", "60");
|
||||
let server_tag = dns_action_server_tag(section_name);
|
||||
let has_inline_domains = length(domain) > 0 || length(domain_suffix) > 0 ||
|
||||
length(domain_keyword) > 0 || length(domain_regex) > 0;
|
||||
|
||||
if (has_inline_domains) {
|
||||
let dns_rule = {
|
||||
action: "route",
|
||||
server: server_tag,
|
||||
rewrite_ttl
|
||||
};
|
||||
add_domain_array(dns_rule, "domain", domain);
|
||||
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);
|
||||
push_dns_matcher_rule(config, dns_rule);
|
||||
}
|
||||
if (length(rule_set_tags) > 0) {
|
||||
push_dns_matcher_rule(config, {
|
||||
action: "route",
|
||||
server: server_tag,
|
||||
rewrite_ttl,
|
||||
rule_set: single_or_array(rule_set_tags)
|
||||
});
|
||||
}
|
||||
if (!has_inline_domains && length(rule_set_tags) == 0)
|
||||
runtime_generate_unsupported("DNS action '" + section_name + "' has no domain matchers");
|
||||
}
|
||||
|
||||
function normalize_port_number_value(value) {
|
||||
return rule_config.normalize_port_number_value(value);
|
||||
}
|
||||
@@ -2504,7 +2584,8 @@ function add_combined_route_for_section(config, section) {
|
||||
section_name,
|
||||
rule_set_tags,
|
||||
dns_rule_set_tags,
|
||||
list_option(section, "domain_ip_lists")
|
||||
list_option(section, "domain_ip_lists"),
|
||||
false
|
||||
);
|
||||
|
||||
let target = runtime_route.target(section, outbound_tag(section_name));
|
||||
@@ -2600,6 +2681,9 @@ function add_outbound_for_section(config, section, taken, sections) {
|
||||
else if (action == "block") {
|
||||
/* route-only action */
|
||||
}
|
||||
else if (action == "dns") {
|
||||
add_dns_server_for_section(config, section);
|
||||
}
|
||||
else {
|
||||
runtime_generate_unsupported("unsupported action " + action);
|
||||
}
|
||||
@@ -2623,7 +2707,10 @@ function reserve_section_outbound_tags(sections, taken) {
|
||||
}
|
||||
|
||||
function add_route_for_section(config, section) {
|
||||
add_combined_route_for_section(config, section);
|
||||
if (option(section, "action", "") == "dns")
|
||||
add_dns_action_rules_for_section(config, section);
|
||||
else
|
||||
add_combined_route_for_section(config, section);
|
||||
}
|
||||
|
||||
function add_service_route_rules(config, sections) {
|
||||
|
||||
@@ -13,6 +13,17 @@ const ACTION_PROVIDERS_AVAILABILITY_EVENT =
|
||||
main.FORKOP_ACTION_PROVIDERS_AVAILABILITY_EVENT ||
|
||||
"forkop:action-providers-availability";
|
||||
const RULE_SET_ITEM_SETTINGS_KEY = "rule_set_settings";
|
||||
const ROUTING_ACTIONS = [
|
||||
"connection",
|
||||
"proxy",
|
||||
"outbound",
|
||||
"vpn",
|
||||
"bypass",
|
||||
"block",
|
||||
"zapret",
|
||||
"zapret2",
|
||||
"byedpi",
|
||||
];
|
||||
const CONNECTIONS_BLOCKED_INTERFACES = [
|
||||
"br-lan",
|
||||
"eth0",
|
||||
@@ -98,6 +109,49 @@ function refreshOutboundDetourSectionOptionValues(option, sectionId) {
|
||||
});
|
||||
}
|
||||
|
||||
function isDnsDetourTargetSection(section, currentSectionId) {
|
||||
const sectionName = getUciSectionName(section);
|
||||
const action = (section && section.action) || "";
|
||||
|
||||
if (
|
||||
!sectionName ||
|
||||
sectionName === currentSectionId ||
|
||||
section.enabled === "0"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (["connection", "proxy", "outbound", "vpn"].includes(action)) {
|
||||
return true;
|
||||
}
|
||||
if (action === "zapret") {
|
||||
return isZapretInstalledForUi();
|
||||
}
|
||||
if (action === "zapret2") {
|
||||
return isZapret2InstalledForUi();
|
||||
}
|
||||
return action === "byedpi" && isByedpiInstalledForUi();
|
||||
}
|
||||
|
||||
function refreshDnsDetourSectionOptionValues(option, sectionId) {
|
||||
option.keylist = [];
|
||||
option.vallist = [];
|
||||
|
||||
(uci.sections(UCI_PACKAGE, "section") || [])
|
||||
.filter((section) => isDnsDetourTargetSection(section, sectionId))
|
||||
.forEach((targetSection) => {
|
||||
option.value(
|
||||
getUciSectionName(targetSection),
|
||||
getUciSectionLabel(targetSection),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function dependsOnRoutingAction(option) {
|
||||
ROUTING_ACTIONS.forEach((action) => option.depends("action", action));
|
||||
return option;
|
||||
}
|
||||
|
||||
const ZAPRET_LEGACY_DEFAULT_NFQWS_OPT =
|
||||
"--filter-tcp=80 <HOSTLIST> --dpi-desync=fake,fakedsplit --dpi-desync-autottl=2 --dpi-desync-fooling=badsum --new --filter-tcp=443 --hostlist=/opt/zapret/ipset/zapret-hosts-google.txt --dpi-desync=fake,multidisorder --dpi-desync-split-pos=1,midsld --dpi-desync-repeats=11 --dpi-desync-fooling=badsum --dpi-desync-fake-tls-mod=rnd,dupsid,sni=www.google.com --new --filter-udp=443 --hostlist=/opt/zapret/ipset/zapret-hosts-google.txt --dpi-desync=fake --dpi-desync-repeats=11 --dpi-desync-fake-quic=/opt/zapret/files/fake/quic_initial_www_google_com.bin --new --filter-udp=443 <HOSTLIST_NOAUTO> --dpi-desync=fake --dpi-desync-repeats=11 --new --filter-tcp=443 <HOSTLIST> --dpi-desync=multidisorder --dpi-desync-split-pos=1,sniext+1,host+1,midsld-2,midsld,midsld+2,endhost-1";
|
||||
|
||||
@@ -3374,6 +3428,8 @@ function getActionOptionLabel(action) {
|
||||
return "Bypass";
|
||||
case "connection":
|
||||
return "Connection";
|
||||
case "dns":
|
||||
return "DNS";
|
||||
case "vpn":
|
||||
return "VPN";
|
||||
case "zapret":
|
||||
@@ -3419,6 +3475,7 @@ function populateActionOptionValues(option) {
|
||||
option.value("connection", getActionOptionLabel("connection"));
|
||||
option.value("bypass", "Bypass");
|
||||
option.value("block", "Block");
|
||||
option.value("dns", "DNS");
|
||||
if (isZapretInstalledForUi()) {
|
||||
option.value("zapret", getActionOptionLabel("zapret"));
|
||||
}
|
||||
@@ -6087,6 +6144,8 @@ function addDynamicConditionField(section, config) {
|
||||
uci.unset(UCI_PACKAGE, section_id, `${config.key}_text`);
|
||||
uci.unset(UCI_PACKAGE, section_id, `${config.key}_text_mode`);
|
||||
};
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
function addLocalDeviceSubnetDynamicField(section, config) {
|
||||
@@ -6195,6 +6254,8 @@ function addTextConditionField(section, config) {
|
||||
config.afterWrite(section_id);
|
||||
}
|
||||
};
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
function loadRulesetValues(option) {
|
||||
@@ -6300,6 +6361,10 @@ function writeBuiltInRulesetReferences(section_id, values) {
|
||||
|
||||
function writeCustomRulesetReferences(section_id, values) {
|
||||
const refs = uniqueDynamicListItems(values);
|
||||
if (getRuleResolvedAction(section_id) === "dns") {
|
||||
writeDnsRulesetReferences(section_id, refs);
|
||||
return;
|
||||
}
|
||||
const subnetRefs = getConfigListValues(
|
||||
section_id,
|
||||
"rule_set_with_subnets",
|
||||
@@ -6315,6 +6380,12 @@ function writeCustomRulesetReferences(section_id, values) {
|
||||
uci.unset(UCI_PACKAGE, section_id, RULE_SET_ITEM_SETTINGS_KEY);
|
||||
}
|
||||
|
||||
function writeDnsRulesetReferences(section_id, values) {
|
||||
writeListOption(section_id, "rule_set", uniqueDynamicListItems(values));
|
||||
uci.unset(UCI_PACKAGE, section_id, "rule_set_with_subnets");
|
||||
uci.unset(UCI_PACKAGE, section_id, RULE_SET_ITEM_SETTINGS_KEY);
|
||||
}
|
||||
|
||||
function createSectionContent(section) {
|
||||
let o;
|
||||
|
||||
@@ -6376,6 +6447,68 @@ function createSectionContent(section) {
|
||||
return this.cfgvalue(section_id);
|
||||
});
|
||||
};
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.ListValue,
|
||||
"dns_type",
|
||||
_("DNS protocol"),
|
||||
_("DNS protocol used by the resolver"),
|
||||
);
|
||||
o.depends("action", "dns");
|
||||
dnsTypeChoices().forEach((choice) => o.value(choice.value, choice.label));
|
||||
o.default = "udp";
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.Value,
|
||||
"dns_server",
|
||||
_("DNS server"),
|
||||
_("DNS server used by the resolver"),
|
||||
);
|
||||
o.depends("action", "dns");
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
o.validate = function (_section_id, value) {
|
||||
const normalized = `${value || ""}`.trim();
|
||||
if (!normalized) {
|
||||
return _("DNS server address cannot be empty");
|
||||
}
|
||||
const validation = main.validateDNS(normalized);
|
||||
return validation.valid ? true : _("Enter a valid DNS server address");
|
||||
};
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.Flag,
|
||||
"dns_detour_enabled",
|
||||
_("DNS through section"),
|
||||
_("Route requests to this DNS server through another section."),
|
||||
);
|
||||
o.depends("action", "dns");
|
||||
o.default = "0";
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.ListValue,
|
||||
"dns_detour_section",
|
||||
_("DNS requests through section"),
|
||||
);
|
||||
o.depends({ action: "dns", dns_detour_enabled: "1" });
|
||||
o.rmempty = false;
|
||||
o.modalonly = true;
|
||||
o.load = function (section_id) {
|
||||
refreshDnsDetourSectionOptionValues(this, section_id);
|
||||
return uci.get(UCI_PACKAGE, section_id, "dns_detour_section") || "";
|
||||
};
|
||||
o.validate = function (_section_id, value) {
|
||||
return value ? true : _("Select a section");
|
||||
};
|
||||
|
||||
o = section.taboption(
|
||||
"settings",
|
||||
form.TextValue,
|
||||
@@ -6993,7 +7126,7 @@ function createSectionContent(section) {
|
||||
},
|
||||
});
|
||||
|
||||
addTextConditionField(section, {
|
||||
const ipConditionOption = addTextConditionField(section, {
|
||||
key: "ip_cidr",
|
||||
optionName: "ip_cidr",
|
||||
legacyTextOptionName: "ip_cidr_text",
|
||||
@@ -7001,13 +7134,14 @@ function createSectionContent(section) {
|
||||
description: _("Match destination IPs or subnets"),
|
||||
textAnalyze: analyzeIpCidrText,
|
||||
});
|
||||
dependsOnRoutingAction(ipConditionOption);
|
||||
|
||||
const builtInRulesetOption = section.taboption(
|
||||
"conditions",
|
||||
form.DynamicList,
|
||||
"community_lists",
|
||||
_("Built-in rule sets"),
|
||||
_("Select a predefined list for routing"),
|
||||
_("Select a predefined domain list"),
|
||||
);
|
||||
builtInRulesetOption.modalonly = true;
|
||||
builtInRulesetOption.placeholder = _("Service list");
|
||||
@@ -7107,9 +7241,12 @@ function createSectionContent(section) {
|
||||
SettingsDynamicList,
|
||||
"rule_set",
|
||||
_("Rule sets"),
|
||||
_("Add URLs or local paths to .srs / .json lists"),
|
||||
_(
|
||||
"Add URLs or local paths to .srs / .json lists. Only domain rules are supported.",
|
||||
),
|
||||
);
|
||||
ruleSetOption.modalonly = true;
|
||||
dependsOnRoutingAction(ruleSetOption);
|
||||
ruleSetOption.renderItemSettingsModal = showRuleSetSettingsModal;
|
||||
ruleSetOption.load = function (section_id) {
|
||||
return getCustomRulesetReferences(section_id);
|
||||
@@ -7126,12 +7263,38 @@ function createSectionContent(section) {
|
||||
return validateCustomRulesetReference(value);
|
||||
};
|
||||
|
||||
const dnsRuleSetOption = section.taboption(
|
||||
"conditions",
|
||||
form.DynamicList,
|
||||
"_dns_rule_set",
|
||||
_("Rule sets"),
|
||||
_(
|
||||
"Add URLs or local paths to .srs / .json lists. Only domain rules are supported.",
|
||||
),
|
||||
);
|
||||
dnsRuleSetOption.depends("action", "dns");
|
||||
dnsRuleSetOption.modalonly = true;
|
||||
dnsRuleSetOption.load = function (section_id) {
|
||||
return getCustomRulesetReferences(section_id);
|
||||
};
|
||||
dnsRuleSetOption.write = function (section_id, value) {
|
||||
writeDnsRulesetReferences(section_id, value);
|
||||
};
|
||||
dnsRuleSetOption.remove = function (section_id) {
|
||||
writeDnsRulesetReferences(section_id, []);
|
||||
};
|
||||
dnsRuleSetOption.validate = function (_section_id, value) {
|
||||
return validateCustomRulesetReference(value);
|
||||
};
|
||||
|
||||
const domainIpListsOption = section.taboption(
|
||||
"conditions",
|
||||
form.DynamicList,
|
||||
"domain_ip_lists",
|
||||
_("Domain and IP Lists"),
|
||||
_("Add URLs or local paths to .lst lists"),
|
||||
_("Domain lists"),
|
||||
_(
|
||||
"Add URLs or local paths to .lst lists. Only domain rules are supported.",
|
||||
),
|
||||
);
|
||||
domainIpListsOption.modalonly = true;
|
||||
domainIpListsOption.load = function (section_id) {
|
||||
@@ -7141,28 +7304,31 @@ function createSectionContent(section) {
|
||||
return validatePlainListReference(value);
|
||||
};
|
||||
|
||||
addLocalDeviceSubnetDynamicField(section, {
|
||||
const sourceIpOption = addLocalDeviceSubnetDynamicField(section, {
|
||||
key: "source_ip_cidr",
|
||||
label: _("Device filter"),
|
||||
description: _(
|
||||
"Apply section rules only to the specified local IP addresses",
|
||||
),
|
||||
});
|
||||
dependsOnRoutingAction(sourceIpOption);
|
||||
|
||||
addLocalDeviceSubnetDynamicField(section, {
|
||||
const fullyRoutedOption = addLocalDeviceSubnetDynamicField(section, {
|
||||
key: "fully_routed_ips",
|
||||
label: _("Forced device routing"),
|
||||
description: _(
|
||||
"All traffic from these IP addresses will be routed through the section unconditionally, ignoring all other conditions.",
|
||||
),
|
||||
});
|
||||
dependsOnRoutingAction(fullyRoutedOption);
|
||||
|
||||
addDynamicConditionField(section, {
|
||||
const portsOption = addDynamicConditionField(section, {
|
||||
key: "ports",
|
||||
label: _("Ports"),
|
||||
description: _("Match destination ports. Use a single port or a range"),
|
||||
dynamicValidate: validatePortCondition,
|
||||
});
|
||||
dependsOnRoutingAction(portsOption);
|
||||
}
|
||||
|
||||
function loadSectionTableOptions(sectionRef) {
|
||||
|
||||
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: FORKOP\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-12 20:23+0300\n"
|
||||
"PO-Revision-Date: 2026-07-12 20:23+0300\n"
|
||||
"POT-Creation-Date: 2026-07-12 00:20+0300\n"
|
||||
"PO-Revision-Date: 2026-07-12 00:20+0300\n"
|
||||
"Last-Translator: ushan0v\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: ru\n"
|
||||
@@ -71,11 +71,11 @@ msgstr "Добавьте хотя бы один DNS-сервер"
|
||||
msgid "Add prefix to nodes"
|
||||
msgstr "Добавлять префикс к узлам"
|
||||
|
||||
msgid "Add URLs or local paths to .lst lists"
|
||||
msgstr "Добавляйте URL или локальные пути к спискам .lst"
|
||||
msgid "Add URLs or local paths to .lst lists. Only domain rules are supported."
|
||||
msgstr "Добавьте URL или локальные пути к спискам .lst. Поддерживаются только доменные правила."
|
||||
|
||||
msgid "Add URLs or local paths to .srs / .json lists"
|
||||
msgstr "Добавьте URL или локальные пути к спискам .srs / .json"
|
||||
msgid "Add URLs or local paths to .srs / .json lists. Only domain rules are supported."
|
||||
msgstr "Добавьте URL или локальные пути к спискам .srs / .json. Поддерживаются только доменные правила."
|
||||
|
||||
msgid "Additional marking rules found"
|
||||
msgstr "Найдены дополнительные правила маркировки"
|
||||
@@ -467,6 +467,9 @@ msgstr "Стратегия DNS"
|
||||
msgid "DNS through proxy"
|
||||
msgstr "DNS через прокси"
|
||||
|
||||
msgid "DNS through section"
|
||||
msgstr "DNS через секцию"
|
||||
|
||||
msgid "DNS Unavailability Timeout"
|
||||
msgstr "Таймаут недоступности DNS"
|
||||
|
||||
@@ -485,8 +488,8 @@ msgstr "Не тестировать указанные серверы."
|
||||
msgid "Does not resolve"
|
||||
msgstr "Не резолвится"
|
||||
|
||||
msgid "Domain and IP Lists"
|
||||
msgstr "Списки доменов и IPs"
|
||||
msgid "Domain lists"
|
||||
msgstr "Списки доменов"
|
||||
|
||||
msgid "Domain Resolver"
|
||||
msgstr "Резолвер доменов"
|
||||
@@ -587,6 +590,9 @@ msgstr "Введите отображаемое имя"
|
||||
msgid "Enter a level name"
|
||||
msgstr "Введите название уровня"
|
||||
|
||||
msgid "Enter a valid DNS server address"
|
||||
msgstr "Укажите корректный адрес DNS-сервера"
|
||||
|
||||
msgid "Enter HWID"
|
||||
msgstr "Укажите HWID"
|
||||
|
||||
@@ -1505,6 +1511,9 @@ msgstr "Маршрут"
|
||||
msgid "Route main DNS requests through the selected section."
|
||||
msgstr "Маршрутизировать основные DNS-запросы через выбранную секцию."
|
||||
|
||||
msgid "Route requests to this DNS server through another section."
|
||||
msgstr "Направлять запросы к этому DNS-серверу через другую секцию."
|
||||
|
||||
msgid "Routing mode"
|
||||
msgstr "Режим маршрутизации"
|
||||
|
||||
@@ -1580,8 +1589,8 @@ msgstr "Выберите распространённый профиль кли
|
||||
msgid "Select a network interface"
|
||||
msgstr "Выберите сетевой интерфейс"
|
||||
|
||||
msgid "Select a predefined list for routing"
|
||||
msgstr "Выберите предустановленный набор правил для маршрутизации"
|
||||
msgid "Select a predefined domain list"
|
||||
msgstr "Выберите готовый список доменов"
|
||||
|
||||
msgid "Select a section"
|
||||
msgstr "Выберите секцию"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -107,7 +107,7 @@ function assertCurrentKeepsStableValues(name, required = [], retired = []) {
|
||||
}
|
||||
}
|
||||
|
||||
assertCurrentKeepsStableValues("action", ["connection", "bypass", "block", "zapret", "zapret2", "byedpi"], ["direct", "proxy", "vpn", "outbound"]);
|
||||
assertCurrentKeepsStableValues("action", ["connection", "bypass", "block", "zapret", "zapret2", "byedpi", "dns"], ["direct", "proxy", "vpn", "outbound"]);
|
||||
assertCurrentKeepsStableValues("protocol", ["tailscale", "vless", "vmess", "trojan", "shadowsocks", "hysteria2", "socks", "mtproto", "json_inbound"]);
|
||||
assertCurrentKeepsStableValues("security", ["reality", "tls", "none"]);
|
||||
assertCurrentKeepsStableValues("transport", ["tcp", "ws", "grpc", "http", "httpupgrade", "xhttp"]);
|
||||
|
||||
@@ -120,6 +120,19 @@ cat >"$WORK_DIR/valid.json" <<'JSON'
|
||||
"enabled": "1",
|
||||
"action": "zapret",
|
||||
"domain_ip_lists": [ "https://example.com/provider.lst" ]
|
||||
},
|
||||
{
|
||||
".name": "custom_dns",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "dns",
|
||||
"dns_type": "dot",
|
||||
"dns_server": "9.9.9.9:5353",
|
||||
"dns_detour_enabled": "1",
|
||||
"dns_detour_section": "proxy",
|
||||
"domain_suffix": [ "dns.example" ],
|
||||
"rule_set": [ "https://example.com/domain-only.srs" ],
|
||||
"domain_ip_lists": [ "https://example.com/domains.lst" ]
|
||||
}
|
||||
],
|
||||
"server": [
|
||||
@@ -226,6 +239,57 @@ cat >"$WORK_DIR/bad-direct-action.json" <<'JSON'
|
||||
JSON
|
||||
assert_rejects "bad direct action" "$WORK_DIR/bad-direct-action.json" "unsupported action 'direct'"
|
||||
|
||||
cat >"$WORK_DIR/bad-dns-rule-server.json" <<'JSON'
|
||||
{
|
||||
"settings": { ".name": "settings", ".type": "settings" },
|
||||
"section": [
|
||||
{ ".name": "dns", ".type": "section", "enabled": "1", "action": "dns", "dns_server": "bad value", "domain_suffix": [ "example.org" ] }
|
||||
]
|
||||
}
|
||||
JSON
|
||||
assert_rejects "bad DNS rule server" "$WORK_DIR/bad-dns-rule-server.json" "invalid DNS server"
|
||||
|
||||
cat >"$WORK_DIR/bad-dns-rule-protocol.json" <<'JSON'
|
||||
{
|
||||
"settings": { ".name": "settings", ".type": "settings" },
|
||||
"section": [
|
||||
{ ".name": "dns", ".type": "section", "enabled": "1", "action": "dns", "dns_type": "tcp", "dns_server": "9.9.9.9", "domain_suffix": [ "example.org" ] }
|
||||
]
|
||||
}
|
||||
JSON
|
||||
assert_rejects "bad DNS rule protocol" "$WORK_DIR/bad-dns-rule-protocol.json" "unsupported protocol 'tcp'"
|
||||
|
||||
cat >"$WORK_DIR/bad-dns-rule-matchers.json" <<'JSON'
|
||||
{
|
||||
"settings": { ".name": "settings", ".type": "settings" },
|
||||
"section": [
|
||||
{ ".name": "dns", ".type": "section", "enabled": "1", "action": "dns", "dns_server": "9.9.9.9" }
|
||||
]
|
||||
}
|
||||
JSON
|
||||
assert_rejects "DNS rule without domain matchers" "$WORK_DIR/bad-dns-rule-matchers.json" "must contain at least one domain condition"
|
||||
|
||||
cat >"$WORK_DIR/bad-dns-rule-subnets.json" <<'JSON'
|
||||
{
|
||||
"settings": { ".name": "settings", ".type": "settings" },
|
||||
"section": [
|
||||
{ ".name": "dns", ".type": "section", "enabled": "1", "action": "dns", "dns_server": "9.9.9.9", "rule_set_with_subnets": [ "https://example.com/mixed.srs" ] }
|
||||
]
|
||||
}
|
||||
JSON
|
||||
assert_rejects "DNS rule subnet extraction" "$WORK_DIR/bad-dns-rule-subnets.json" "domain-only rule sets"
|
||||
|
||||
cat >"$WORK_DIR/bad-dns-rule-detour.json" <<'JSON'
|
||||
{
|
||||
"settings": { ".name": "settings", ".type": "settings" },
|
||||
"section": [
|
||||
{ ".name": "dns", ".type": "section", "enabled": "1", "action": "dns", "dns_server": "9.9.9.9", "dns_detour_enabled": "1", "dns_detour_section": "bypass", "domain_suffix": [ "example.org" ] },
|
||||
{ ".name": "bypass", ".type": "section", "enabled": "1", "action": "bypass", "domain_suffix": [ "bypass.example" ] }
|
||||
]
|
||||
}
|
||||
JSON
|
||||
assert_rejects "DNS rule unsupported detour" "$WORK_DIR/bad-dns-rule-detour.json" "unsupported action 'bypass'"
|
||||
|
||||
cat >"$WORK_DIR/bad-port.json" <<'JSON'
|
||||
{
|
||||
"settings": { ".name": "settings", ".type": "settings" },
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SECTION_JS="$ROOT_DIR/luci-app-forkop/htdocs/luci-static/resources/view/forkop/section.js"
|
||||
|
||||
node - "$SECTION_JS" <<'NODE'
|
||||
const fs = require("fs");
|
||||
const source = fs.readFileSync(process.argv[2], "utf8");
|
||||
|
||||
function fail(message) {
|
||||
console.error(`FAIL: ${message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function requiredIndex(needle, from = 0) {
|
||||
const index = source.indexOf(needle, from);
|
||||
if (index < 0) fail(`missing source contract: ${needle}`);
|
||||
return index;
|
||||
}
|
||||
|
||||
const domains = requiredIndex('key: "domain_suffix"');
|
||||
const ips = requiredIndex('const ipConditionOption = addTextConditionField(section, {', domains);
|
||||
const builtIns = requiredIndex('const builtInRulesetOption = section.taboption(', ips);
|
||||
if (source.slice(domains, ips).includes("dependsOnRoutingAction")) {
|
||||
fail("DNS action must keep the Domains field visible");
|
||||
}
|
||||
if (!source.slice(ips, builtIns).includes("dependsOnRoutingAction(ipConditionOption)")) {
|
||||
fail("DNS action must hide the IPs field");
|
||||
}
|
||||
|
||||
const sectionContent = requiredIndex("function createSectionContent(section)");
|
||||
const protocol = requiredIndex('"dns_type"', sectionContent);
|
||||
const server = requiredIndex('"dns_server"', protocol);
|
||||
if (server <= protocol || !source.slice(protocol, server).includes('_("DNS protocol")')) {
|
||||
fail("DNS protocol must be rendered before DNS server");
|
||||
}
|
||||
if (source.slice(server, requiredIndex('"dns_detour_enabled"', server)).includes('placeholder = "1.1.1.1"')) {
|
||||
fail("DNS server must not render the 1.1.1.1 placeholder");
|
||||
}
|
||||
|
||||
for (const expected of [
|
||||
'_("DNS server used by the resolver")',
|
||||
'_("DNS requests through section")',
|
||||
'"Add URLs or local paths to .srs / .json lists. Only domain rules are supported."',
|
||||
'"Add URLs or local paths to .lst lists. Only domain rules are supported."',
|
||||
]) {
|
||||
requiredIndex(expected);
|
||||
}
|
||||
|
||||
const ruleSetOption = requiredIndex("const ruleSetOption = section.taboption(");
|
||||
const dnsRuleSetOption = requiredIndex("const dnsRuleSetOption = section.taboption(", ruleSetOption);
|
||||
const domainLists = requiredIndex("const domainIpListsOption", dnsRuleSetOption);
|
||||
if (!source.slice(ruleSetOption, dnsRuleSetOption).includes("SettingsDynamicList") ||
|
||||
!source.slice(ruleSetOption, dnsRuleSetOption).includes("dependsOnRoutingAction(ruleSetOption)")) {
|
||||
fail("routing rule sets must keep their settings DynamicList");
|
||||
}
|
||||
if (!source.slice(dnsRuleSetOption, domainLists).includes("form.DynamicList") ||
|
||||
!source.slice(dnsRuleSetOption, domainLists).includes('"_dns_rule_set"') ||
|
||||
!source.slice(dnsRuleSetOption, domainLists).includes('dnsRuleSetOption.depends("action", "dns")') ||
|
||||
!source.slice(dnsRuleSetOption, domainLists).includes("writeDnsRulesetReferences")) {
|
||||
fail("DNS rule sets must use a dedicated classic DynamicList");
|
||||
}
|
||||
NODE
|
||||
|
||||
printf 'DNS action UI checks passed\n'
|
||||
+14
-1
@@ -457,6 +457,16 @@ cat >"$WORK_DIR/populate-fixture.json" <<'JSON'
|
||||
"action": "proxy",
|
||||
"domain_suffix": [ "example.org" ],
|
||||
"ports": [ "8443" ]
|
||||
},
|
||||
{
|
||||
".name": "dns_only",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "dns",
|
||||
"domain_suffix": [ "dns-only.example" ],
|
||||
"ip_cidr": [ "192.0.2.53" ],
|
||||
"ports": [ "5353" ],
|
||||
"fully_routed_ips": [ "192.168.1.53/32" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -554,6 +564,9 @@ fi
|
||||
if grep -Fq $'nft\tadd\telement\tinet\tForkopTable\tforkop_rule_ports_with_domain_ports\t{ 8443 }' "$NFT_LOG"; then
|
||||
fail "ports with destination matchers should not populate global port set"
|
||||
fi
|
||||
if grep -Fq '192.0.2.53' "$NFT_LOG" || grep -Fq '192.168.1.53' "$NFT_LOG" || grep -Fq 'forkop_rule_dns_only' "$NFT_LOG"; then
|
||||
fail "DNS action should not populate nft sets or fully routed rules"
|
||||
fi
|
||||
if [ "$(grep -F $'ip\tsaddr\t192.168.1.20/32' "$NFT_LOG" | wc -l | tr -d ' ')" != "3" ]; then
|
||||
fail "duplicate fully routed source should insert exactly one tcp/udp/local rule set"
|
||||
fi
|
||||
@@ -717,7 +730,7 @@ nft_ucode nft-populate-runtime-sets-fixture "$WORK_DIR/populate-fixture.json" 0
|
||||
|
||||
: > "$NFT_LOG"
|
||||
NFT_MANGLE_CHAIN_OUTPUT='ip saddr 192.168.1.20 meta l4proto tcp meta mark set 0x00100000 counter' \
|
||||
nft_ucode nft-populate-runtime-sets-fixture "$WORK_DIR/populate-fixture.json" 1 "disabled deferred inline inline_no_ports ports_only ports_with_domain" ForkopTable forkop_subnets forkop_ports forkop_ip_ports forkop_interfaces localv4 0x00100000
|
||||
nft_ucode nft-populate-runtime-sets-fixture "$WORK_DIR/populate-fixture.json" 1 "disabled deferred inline inline_no_ports ports_only ports_with_domain dns_only" ForkopTable forkop_subnets forkop_ports forkop_ip_ports forkop_interfaces localv4 0x00100000
|
||||
if grep -Fq $'nft\tinsert\trule' "$NFT_LOG"; then
|
||||
fail "existing nft-normalized /32 fully routed source should not insert"
|
||||
fi
|
||||
|
||||
@@ -63,23 +63,31 @@ if state_ucode community-service-has-subnet-list youtube >/dev/null 2>&1; then
|
||||
fail "youtube should not be a subnet-list community service"
|
||||
fi
|
||||
|
||||
state_ucode rule-has-list-update-source 1 "youtube telegram" "" "" "" "" >/dev/null ||
|
||||
state_ucode rule-has-list-update-source 1 proxy "youtube telegram" "" "" "" "" >/dev/null ||
|
||||
fail "community subnet service should require list update"
|
||||
state_ucode rule-has-list-update-source 1 "" "https://example.com/domains.lst" "" "" "" >/dev/null ||
|
||||
state_ucode rule-has-list-update-source 1 proxy "" "https://example.com/domains.lst" "" "" "" >/dev/null ||
|
||||
fail "remote domain list should require list update"
|
||||
state_ucode rule-has-list-update-source 1 "" "" "" "" "local.lst https://example.com/mixed.lst" >/dev/null ||
|
||||
state_ucode rule-has-list-update-source 1 proxy "" "" "" "" "local.lst https://example.com/mixed.lst" >/dev/null ||
|
||||
fail "remote domain_ip list should require list update"
|
||||
if state_ucode rule-has-list-update-source 0 "telegram" "https://example.com/domains.lst" "" "" "" >/dev/null 2>&1; then
|
||||
if state_ucode rule-has-list-update-source 0 proxy "telegram" "https://example.com/domains.lst" "" "" "" >/dev/null 2>&1; then
|
||||
fail "disabled rule should not require list update"
|
||||
fi
|
||||
state_ucode rule-has-list-update-source 1 dns "" "" "" "" "https://example.com/domains.lst" >/dev/null ||
|
||||
fail "remote DNS domain list should require list update"
|
||||
if state_ucode rule-has-list-update-source 1 dns "telegram" "" "" "" "" >/dev/null 2>&1; then
|
||||
fail "DNS rules should not import built-in subnet lists"
|
||||
fi
|
||||
|
||||
state_ucode rule-has-nft-list-update-source 1 "telegram" "" "" "" >/dev/null ||
|
||||
state_ucode rule-has-nft-list-update-source 1 proxy "telegram" "" "" "" >/dev/null ||
|
||||
fail "community subnet service should require nft list update"
|
||||
state_ucode rule-has-nft-list-update-source 1 "" "https://example.com/subnets.lst" "" "" >/dev/null ||
|
||||
state_ucode rule-has-nft-list-update-source 1 proxy "" "https://example.com/subnets.lst" "" "" >/dev/null ||
|
||||
fail "remote subnet list should require nft list update"
|
||||
if state_ucode rule-has-nft-list-update-source 1 "" "" "" "local.lst" >/dev/null 2>&1; then
|
||||
if state_ucode rule-has-nft-list-update-source 1 proxy "" "" "" "local.lst" >/dev/null 2>&1; then
|
||||
fail "local-only domain_ip list should not require nft list update"
|
||||
fi
|
||||
if state_ucode rule-has-nft-list-update-source 1 dns "telegram" "https://example.com/subnets.lst" "" "https://example.com/mixed.lst" >/dev/null 2>&1; then
|
||||
fail "DNS rules should never require nft list updates"
|
||||
fi
|
||||
|
||||
state_ucode rule-has-subscription-update-source 1 "1h" >/dev/null ||
|
||||
fail "subscription proxy with update interval should require subscription update"
|
||||
@@ -1082,12 +1090,25 @@ cat >"$WORK_DIR/reload-state-signatures.json" <<'JSON'
|
||||
"action": "proxy",
|
||||
"urltest_enabled": "1",
|
||||
"urltest_check_interval": "7m"
|
||||
},
|
||||
{
|
||||
".name": "dns_only",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "dns",
|
||||
"ports": [ "5353" ],
|
||||
"community_lists": [ "telegram" ],
|
||||
"remote_subnet_lists": [ "https://example.com/ignored-subnets.lst" ],
|
||||
"rule_set_with_subnets": [ "https://example.com/ignored-subnets.srs" ],
|
||||
"domain_ip_lists": [ "https://example.com/dns-domains.lst" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/list-signature.expected" <<'EOF_LIST_SIG'
|
||||
[lists.list_proxy.action]
|
||||
proxy
|
||||
[lists.list_proxy.ports]
|
||||
443,80,443-444
|
||||
[lists.list_proxy.community_subnet_lists]
|
||||
@@ -1100,6 +1121,8 @@ https://example.com/subnets.lst
|
||||
https://example.com/subnets.srs
|
||||
[lists.list_proxy.domain_ip_lists]
|
||||
local.lst https://example.com/mixed.lst
|
||||
[lists.sub_paused.action]
|
||||
proxy
|
||||
[lists.sub_paused.ports]
|
||||
|
||||
[lists.sub_paused.community_subnet_lists]
|
||||
@@ -1112,6 +1135,8 @@ local.lst https://example.com/mixed.lst
|
||||
|
||||
[lists.sub_paused.domain_ip_lists]
|
||||
|
||||
[lists.implicit_action.action]
|
||||
|
||||
[lists.implicit_action.ports]
|
||||
|
||||
[lists.implicit_action.community_subnet_lists]
|
||||
@@ -1124,6 +1149,8 @@ local.lst https://example.com/mixed.lst
|
||||
|
||||
[lists.implicit_action.domain_ip_lists]
|
||||
|
||||
[lists.urltest_custom.action]
|
||||
proxy
|
||||
[lists.urltest_custom.ports]
|
||||
|
||||
[lists.urltest_custom.community_subnet_lists]
|
||||
@@ -1136,6 +1163,10 @@ local.lst https://example.com/mixed.lst
|
||||
|
||||
[lists.urltest_custom.domain_ip_lists]
|
||||
|
||||
[lists.dns_only.action]
|
||||
dns
|
||||
[lists.dns_only.domain_ip_lists]
|
||||
https://example.com/dns-domains.lst
|
||||
EOF_LIST_SIG
|
||||
|
||||
cat >"$WORK_DIR/cron-signature.expected" <<'EOF_CRON_SIG'
|
||||
@@ -1143,6 +1174,8 @@ cat >"$WORK_DIR/cron-signature.expected" <<'EOF_CRON_SIG'
|
||||
6h
|
||||
[settings.component_update_check_interval]
|
||||
2h
|
||||
[lists.list_proxy.action]
|
||||
proxy
|
||||
[lists.list_proxy.ports]
|
||||
443,80,443-444
|
||||
[lists.list_proxy.community_subnet_lists]
|
||||
@@ -1155,6 +1188,8 @@ https://example.com/subnets.lst
|
||||
https://example.com/subnets.srs
|
||||
[lists.list_proxy.domain_ip_lists]
|
||||
local.lst https://example.com/mixed.lst
|
||||
[lists.sub_paused.action]
|
||||
proxy
|
||||
[lists.sub_paused.ports]
|
||||
|
||||
[lists.sub_paused.community_subnet_lists]
|
||||
@@ -1167,6 +1202,8 @@ local.lst https://example.com/mixed.lst
|
||||
|
||||
[lists.sub_paused.domain_ip_lists]
|
||||
|
||||
[lists.implicit_action.action]
|
||||
|
||||
[lists.implicit_action.ports]
|
||||
|
||||
[lists.implicit_action.community_subnet_lists]
|
||||
@@ -1179,6 +1216,8 @@ local.lst https://example.com/mixed.lst
|
||||
|
||||
[lists.implicit_action.domain_ip_lists]
|
||||
|
||||
[lists.urltest_custom.action]
|
||||
proxy
|
||||
[lists.urltest_custom.ports]
|
||||
|
||||
[lists.urltest_custom.community_subnet_lists]
|
||||
@@ -1191,6 +1230,10 @@ local.lst https://example.com/mixed.lst
|
||||
|
||||
[lists.urltest_custom.domain_ip_lists]
|
||||
|
||||
[lists.dns_only.action]
|
||||
dns
|
||||
[lists.dns_only.domain_ip_lists]
|
||||
https://example.com/dns-domains.lst
|
||||
[subscription.list_proxy.subscription_urls]
|
||||
[ { "url": "https://example.com/sub1.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "prefix_nodes": "0", "node_prefix": "", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" }, { "url": "https://example.com/sub2.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "show_dashboard_metadata": "1", "prefix_nodes": "0", "node_prefix": "", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
|
||||
[subscription.list_proxy.subscription_update_interval]
|
||||
@@ -1232,6 +1275,25 @@ assert_eq "7m" \
|
||||
state_ucode has-subscription-update-sources-fixture "$WORK_DIR/reload-state-signatures.json" >/dev/null ||
|
||||
fail "fixture should have subscription update sources"
|
||||
|
||||
cat >"$WORK_DIR/dns-action-signature-udp.json" <<'JSON'
|
||||
{
|
||||
"section": [
|
||||
{ ".name": "dns", ".type": "section", "enabled": "1", "action": "dns", "dns_type": "udp", "dns_server": "9.9.9.9", "domain_suffix": [ "example.org" ] }
|
||||
]
|
||||
}
|
||||
JSON
|
||||
cat >"$WORK_DIR/dns-action-signature-dot.json" <<'JSON'
|
||||
{
|
||||
"section": [
|
||||
{ ".name": "dns", ".type": "section", "enabled": "1", "action": "dns", "dns_type": "dot", "dns_server": "9.9.9.9", "domain_suffix": [ "example.org" ] }
|
||||
]
|
||||
}
|
||||
JSON
|
||||
if [ "$(state_ucode sing-box-signature-fixture "$WORK_DIR/dns-action-signature-udp.json")" = \
|
||||
"$(state_ucode sing-box-signature-fixture "$WORK_DIR/dns-action-signature-dot.json")" ]; then
|
||||
fail "DNS action protocol should change the sing-box signature"
|
||||
fi
|
||||
|
||||
cat >"$WORK_DIR/reload-state.expected" <<EOF_RELOAD_STATE
|
||||
format=1
|
||||
service_trigger_signature=$(state_ucode service-trigger-signature-fixture "$WORK_DIR/reload-state-signatures.json")
|
||||
|
||||
@@ -323,6 +323,66 @@ cat >"$WORK_DIR/runtime-matchers-fixture.json" <<'JSON'
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/dns-action.lst" <<'EOF_DNS_ACTION_LIST'
|
||||
list-dns.example
|
||||
198.51.100.0/24
|
||||
EOF_DNS_ACTION_LIST
|
||||
cat >"$WORK_DIR/dns-action-fixture.json" <<JSON
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
".type": "settings",
|
||||
"config_path": "/tmp/sing-box/config.json",
|
||||
"dns_server": "1.1.1.1",
|
||||
"service_listen_address": "127.0.0.1"
|
||||
},
|
||||
"section": [
|
||||
{
|
||||
".name": "dns_first",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "dns",
|
||||
"dns_type": "dot",
|
||||
"dns_server": "9.9.9.9:5353",
|
||||
"dns_detour_enabled": "1",
|
||||
"dns_detour_section": "detour",
|
||||
"domain_suffix": [ "dns-first.example" ],
|
||||
"community_lists": [ "youtube" ],
|
||||
"rule_set": [ "https://example.com/domain-only.srs" ],
|
||||
"domain_ip_lists": [ "$WORK_DIR/dns-action.lst" ],
|
||||
"ip_cidr": [ "203.0.113.0/24" ],
|
||||
"source_ip_cidr": [ "192.0.2.1/32" ],
|
||||
"fully_routed_ips": [ "192.0.2.2/32" ],
|
||||
"ports": [ "443" ]
|
||||
},
|
||||
{
|
||||
".name": "proxy",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"outbound_jsons": [ "{\"type\":\"direct\"}" ],
|
||||
"domain_suffix": [ "dns-first.example", "proxy-first.example" ]
|
||||
},
|
||||
{
|
||||
".name": "dns_second",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "dns",
|
||||
"dns_server": "8.8.8.8",
|
||||
"domain_suffix": [ "proxy-first.example" ]
|
||||
},
|
||||
{
|
||||
".name": "detour",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "connection",
|
||||
"outbound_jsons": [ "{\"type\":\"direct\"}" ],
|
||||
"domain_suffix": [ "detour.example" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
|
||||
cat >"$WORK_DIR/urltest-filter-fixture.json" <<'JSON'
|
||||
{
|
||||
"settings": {
|
||||
@@ -904,6 +964,7 @@ generate_config "$WORK_DIR/disabled-updates-fixture.json" "$WORK_DIR/disabled.js
|
||||
generate_config "$WORK_DIR/default-updates-fixture.json" "$WORK_DIR/default.json"
|
||||
generate_config "$WORK_DIR/server-inbound-fixture.json" "$WORK_DIR/server.json"
|
||||
generate_config "$WORK_DIR/runtime-matchers-fixture.json" "$WORK_DIR/matchers.json"
|
||||
generate_config "$WORK_DIR/dns-action-fixture.json" "$WORK_DIR/dns-action.json"
|
||||
generate_config "$WORK_DIR/urltest-filter-fixture.json" "$WORK_DIR/urltest.json"
|
||||
generate_config "$WORK_DIR/provider-actions-fixture.json" "$WORK_DIR/providers.json"
|
||||
generate_config "$WORK_DIR/manual-transport-fixture.json" "$WORK_DIR/manual.json"
|
||||
@@ -1036,6 +1097,14 @@ function dns_rule(config, predicate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function dns_rule_index(config, predicate) {
|
||||
let rules = config.dns.rules || [];
|
||||
for (let i = 0; i < length(rules); i++)
|
||||
if (rules[i] && predicate(rules[i]))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
function dns_server(config, predicate) {
|
||||
for (let server in config.dns.servers || [])
|
||||
if (server && predicate(server))
|
||||
@@ -1087,6 +1156,27 @@ assert(route_rule(matchers, r => contains(r.inbound, "tproxy-in") && contains(r.
|
||||
assert(route_rule(matchers, r => r.outbound == "direct-out" && contains(r.source_ip_cidr, "192.168.1.5/32")) == null, "routing excluded source removed");
|
||||
assert(route_rule(matchers, r => r.outbound == "proxy-out" && contains(r.source_ip_cidr, "10.0.0.2/32") && contains(r.source_ip_cidr, "2001:db8::2/128")) != null, "source_ip_cidr matcher");
|
||||
|
||||
let dns_action = cfg("dns-action");
|
||||
let dns_first_server = dns_server(dns_action, r => r.tag == "dns_first-dns-server");
|
||||
assert(dns_first_server && dns_first_server.type == "tls" && dns_first_server.server == "9.9.9.9" && dns_first_server.server_port == 5353, "DNS action protocol and server");
|
||||
assert(dns_first_server.detour == "detour-out", "DNS action detour");
|
||||
assert(dns_server(dns_action, r => r.tag == "dns_second-dns-server" && r.server == "8.8.8.8") != null, "second DNS action server");
|
||||
assert(outbound(dns_action, "dns_first-out") == null && outbound(dns_action, "dns_second-out") == null, "DNS actions do not create outbounds");
|
||||
assert(route_rule(dns_action, r => contains(r.domain_suffix, "dns-first.example") && r.outbound != null) != null, "proxy still owns its route rule");
|
||||
assert(route_rule(dns_action, r => contains(r.domain_suffix, "list-dns.example")) == null, "DNS action does not create route rules");
|
||||
let dns_first_index = dns_rule_index(dns_action, r => contains(r.domain_suffix, "dns-first.example") && r.server == "dns_first-dns-server");
|
||||
let proxy_after_dns_index = dns_rule_index(dns_action, r => contains(r.domain_suffix, "dns-first.example") && r.server == "fakeip-server");
|
||||
assert(dns_first_index >= 0 && proxy_after_dns_index > dns_first_index, "higher DNS action wins over lower proxy DNS rule");
|
||||
let proxy_first_index = dns_rule_index(dns_action, r => contains(r.domain_suffix, "proxy-first.example") && r.server == "fakeip-server");
|
||||
let dns_after_proxy_index = dns_rule_index(dns_action, r => contains(r.domain_suffix, "proxy-first.example") && r.server == "dns_second-dns-server");
|
||||
assert(proxy_first_index >= 0 && dns_after_proxy_index > proxy_first_index, "higher proxy DNS rule wins over lower DNS action");
|
||||
assert(dns_rule(dns_action, r => contains(r.rule_set, "dns_first-youtube-community-ruleset") && r.server == "dns_first-dns-server") != null, "DNS action built-in domain rule set");
|
||||
let dns_custom_ruleset = ruleset_url(dns_action, "https://example.com/domain-only.srs");
|
||||
assert(dns_custom_ruleset && dns_rule(dns_action, r => contains(r.rule_set, dns_custom_ruleset.tag) && r.server == "dns_first-dns-server") != null, "DNS action custom domain rule set");
|
||||
let dns_list = json(fs.readfile(dir + "/dns-action.json.rulesets/dns_first-lists-ruleset.json"));
|
||||
assert(index(sprintf("%J", dns_list), "list-dns.example") >= 0, "DNS action imports domains from plain lists");
|
||||
assert(index(sprintf("%J", dns_list), "198.51.100.0/24") < 0, "DNS action drops subnets from plain lists");
|
||||
|
||||
let urltest = cfg("urltest");
|
||||
let urltest_out = outbound(urltest, "proxy-urltest-out");
|
||||
assert(urltest_out && length(urltest_out.outbounds) == 1 && urltest_out.outbounds[0] == "proxy-1-out", "URLTest include filter");
|
||||
|
||||
Reference in New Issue
Block a user