Files
NeoTachyon/tachyon/files/usr/lib/singbox/generator_routes.uc
T

1576 lines
58 KiB
Ucode

#!/usr/bin/env ucode
let fs = require("fs");
let common = require("core.common");
let helpers = require("core.helpers");
let runtime_constants = require("singbox.constants");
let runtime_dns = require("singbox.dns");
let runtime_route = require("singbox.route");
let runtime_rulesets = require("singbox.rulesets");
let runtime_subscription = require("singbox.subscription");
let runtime_servers = require("singbox.servers");
let runtime_urltest = require("singbox.urltest");
let connections = require("config.connections");
let rule_config = require("config.rule");
let source_rulesets = require("routing.rulesets");
let as_string = common.as_string;
let option = common.option;
let list_option = common.list_option;
let bool_option = common.bool_option;
let int_option = common.int_option;
let array_or_empty = common.array_or_empty;
let object_or_empty = common.object_or_empty;
let outbound_tag = runtime_constants.outbound_tag;
let tag = runtime_constants.tag;
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
let runtime_ruleset_folder = getenv("TACHYON_RULESET_FOLDER") || "/usr/share/tachyon/rulesets";
let ctx = {};
function settings_update_interval() {
let settings = ctx.runtime_settings();
if (!bool_option(settings, "list_update_enabled", true))
return "";
let update_interval = option(settings, "update_interval", "1d");
return update_interval != "" ? update_interval : "1d";
}
function remote_ruleset_update_interval() {
let update_interval = settings_update_interval();
return update_interval != "" ? update_interval : runtime_constants.DISABLED_UPDATE_INTERVAL;
}
function init(c) {
ctx = c;
}
function valid_section_name(name) {
name = as_string(name);
return match(name, /^[A-Za-z0-9_]+$/);
}
function section_enabled(section) {
return bool_option(section, "enabled", true);
}
function ruleset_tag(section_name, name, kind) {
return kind == ""
? section_name + "-" + name + "-ruleset"
: section_name + "-" + name + "-" + kind + "-ruleset";
}
function ruleset_registered(config, tag_name) {
for (let rule_set in array_or_empty(config.route && config.route.rule_set)) {
if (type(rule_set) == "object" && rule_set.tag == tag_name)
return true;
}
return false;
}
function duration_to_seconds(value) {
value = as_string(value);
if (value == "")
return null;
if (match(value, /^[0-9]+$/) != null)
return int(value, 10);
let suffix = substr(value, length(value) - 1);
let number = substr(value, 0, length(value) - 1);
if (match(number, /^[0-9]+$/) == null)
return null;
let multiplier = null;
if (suffix == "s")
multiplier = 1;
else if (suffix == "m")
multiplier = 60;
else if (suffix == "h")
multiplier = 3600;
else if (suffix == "d")
multiplier = 86400;
return multiplier == null ? null : int(number, 10) * multiplier;
}
function urltest_check_interval(section, urltest_id) {
let interval = connections.urltest_check_interval(section, urltest_id);
return interval != "" ? interval : "3m";
}
function legacy_urltest_idle_timeout(section, urltest_id) {
if (urltest_id != "urltest")
return "";
let settings = connections.urltest_settings(section, urltest_id);
if (type(settings) == "object" && as_string(settings[".type"] || "") == "urltest")
return "";
let interval = urltest_check_interval(section, urltest_id);
let interval_seconds = duration_to_seconds(interval);
let default_idle_seconds = duration_to_seconds(runtime_constants.URLTEST_DEFAULT_IDLE_TIMEOUT);
return interval_seconds != null && interval_seconds > default_idle_seconds ? interval : "";
}
function urltest_idle_timeout(section, urltest_id) {
let configured = connections.urltest_idle_timeout(section, urltest_id);
return configured != "" ? configured : legacy_urltest_idle_timeout(section, urltest_id);
}
function supported_urltest_filter_mode(mode) {
return mode == "include" || mode == "exclude" || mode == "mixed";
}
function filter_mode_uses_include(mode) {
return mode == "include" || mode == "mixed";
}
function filter_mode_uses_exclude(mode) {
return mode == "exclude" || mode == "mixed";
}
function configured_country_filter(mode, include_countries, exclude_countries) {
return (filter_mode_uses_include(mode) && length(array_or_empty(include_countries)) > 0) ||
(filter_mode_uses_exclude(mode) && length(array_or_empty(exclude_countries)) > 0);
}
function section_needs_country_is(section) {
let dashboard_mode = connections.dashboard_filter_mode(section);
if (connections.dashboard_detect_server_country(section) == "country_is" &&
configured_country_filter(
dashboard_mode,
connections.dashboard_include_countries(section),
connections.dashboard_exclude_countries(section)
))
return true;
for (let urltest_id in connections.urltests(section)) {
let mode = connections.urltest_filter_mode(section, urltest_id);
if (connections.urltest_detect_server_country(section, urltest_id) == "country_is" &&
configured_country_filter(
mode,
connections.urltest_include_countries(section, urltest_id),
connections.urltest_exclude_countries(section, urltest_id)
))
return true;
}
for (let group_id in connections.priority_groups(section)) {
for (let level_id in connections.priority_levels(group_id)) {
if (connections.priority_level_direct(group_id, level_id))
continue;
let mode = connections.priority_level_filter_mode(group_id, level_id);
if (connections.priority_level_detect_server_country(group_id, level_id) == "country_is" &&
configured_country_filter(
mode,
connections.priority_level_include_countries(group_id, level_id),
connections.priority_level_exclude_countries(group_id, level_id)
))
return true;
}
}
return false;
}
function section_has_direct_priority_level(section) {
for (let group_id in connections.priority_groups(section))
for (let level_id in connections.priority_levels(group_id))
if (connections.priority_level_direct(group_id, level_id))
return true;
return false;
}
function urltest_country_metadata(section, urltest_id, state) {
let metadata = object_or_empty(object_or_empty(state.outboundMetadata).countries);
let detect_method = connections.urltest_detect_server_country(section, urltest_id);
if (detect_method == "flag_emoji")
return runtime_urltest.countries_from_flag_names(object_or_empty(object_or_empty(state.outboundMetadata).names));
return metadata;
}
function array_contains(values, needle) {
for (let value in array_or_empty(values)) {
if (value == needle)
return true;
}
return false;
}
function unique_string_array(values) {
let result = [];
let seen = {};
for (let value in array_or_empty(values)) {
value = as_string(value);
if (value == "" || seen[value])
continue;
seen[value] = true;
push(result, value);
}
return result;
}
function object_keys_set(values) {
let result = {};
for (let value in array_or_empty(values))
result[value] = true;
return result;
}
function tag_display_name(tag, names) {
let name = as_string(object_or_empty(names)[tag] || "");
return name != "" ? name : tag;
}
function regex_match_set(tags, names, regexes) {
return object_keys_set(runtime_urltest.regex_matching_tag_array(tags, names, regexes));
}
function tag_name_filter_matches(tag, names, name_filter, regex_set) {
let name = tag_display_name(tag, names);
return array_contains(name_filter, name) || array_contains(name_filter, tag) || regex_set[tag];
}
function tag_country_filter_matches(tag, countries, country_filter) {
let country = uc(as_string(object_or_empty(countries)[tag] || ""));
return country != "" && array_contains(country_filter, country);
}
function tag_attribute_filter_matches(tag, metadata, selected_values) {
selected_values = array_or_empty(selected_values);
if (length(selected_values) == 0)
return true;
let value = lc(as_string(object_or_empty(metadata)[tag] || ""));
if (value == "")
return false;
for (let selected in selected_values)
if (lc(as_string(selected)) == value)
return true;
return false;
}
function proxy_parameter_filter_matches_all(tag, metadata, protocols, transports, securities) {
metadata = object_or_empty(metadata);
return tag_attribute_filter_matches(tag, metadata.protocols, protocols) &&
tag_attribute_filter_matches(tag, metadata.transports, transports) &&
tag_attribute_filter_matches(tag, metadata.securities, securities);
}
function proxy_parameter_filter_matches_any(tag, metadata, protocols, transports, securities) {
metadata = object_or_empty(metadata);
return (length(array_or_empty(protocols)) > 0 &&
tag_attribute_filter_matches(tag, metadata.protocols, protocols)) ||
(length(array_or_empty(transports)) > 0 &&
tag_attribute_filter_matches(tag, metadata.transports, transports)) ||
(length(array_or_empty(securities)) > 0 &&
tag_attribute_filter_matches(tag, metadata.securities, securities));
}
function name_or_country_filter_configured(name_filter, regexes, country_filter) {
return length(array_or_empty(name_filter)) > 0 ||
length(array_or_empty(regexes)) > 0 ||
length(array_or_empty(country_filter)) > 0;
}
function urltest_all_candidate_outbounds(urltest_candidate_tags) {
return unique_string_array(urltest_candidate_tags);
}
function urltest_matching_candidate_outbounds(urltest_candidate_tags, names, countries, name_filter, regexes, country_filter,
metadata, proxy_parameters_enabled, proxy_parameters_operator, protocols, transports, securities, additional_matches) {
names = object_or_empty(names);
countries = object_or_empty(countries);
country_filter = runtime_urltest.normalized_country_list(country_filter);
let regex_set = regex_match_set(urltest_candidate_tags, names, regexes);
let base_filter_configured = name_or_country_filter_configured(name_filter, regexes, country_filter);
let additional_set = object_keys_set(additional_matches);
let result = [];
for (let tag in array_or_empty(urltest_candidate_tags)) {
let base_matches = tag_name_filter_matches(tag, names, name_filter, regex_set) ||
tag_country_filter_matches(tag, countries, country_filter);
let matches = additional_set[tag] || base_matches;
if (proxy_parameters_enabled && proxy_parameters_operator == "or") {
matches = additional_set[tag] || base_matches || proxy_parameter_filter_matches_any(
tag, metadata, protocols, transports, securities
);
}
else if (proxy_parameters_enabled) {
if (!base_filter_configured)
base_matches = true;
matches = additional_set[tag] ||
(base_matches && proxy_parameter_filter_matches_all(
tag, metadata, protocols, transports, securities
));
}
if (matches)
push(result, tag);
}
return unique_string_array(result);
}
function urltest_exclude_outbounds(all_outbounds, excluded_outbounds) {
let excluded = object_keys_set(excluded_outbounds);
let result = [];
for (let tag in array_or_empty(all_outbounds)) {
if (!excluded[tag])
push(result, tag);
}
return result;
}
function filter_candidate_outbounds(filter_mode, urltest_candidate_tags, names, countries, metadata,
include_names, include_regex, include_countries,
include_proxy_parameters, include_protocols, include_transports, include_securities,
exclude_names, exclude_regex, exclude_countries,
exclude_proxy_parameters, exclude_protocols, exclude_transports, exclude_securities,
include_additional_matches, exclude_additional_matches) {
let all_outbounds = urltest_all_candidate_outbounds(urltest_candidate_tags);
if (filter_mode == "" || filter_mode == "disabled")
return all_outbounds;
if (!supported_urltest_filter_mode(filter_mode))
return all_outbounds;
let include_outbounds = urltest_matching_candidate_outbounds(
urltest_candidate_tags,
names,
countries,
include_names,
include_regex,
include_countries,
metadata,
include_proxy_parameters,
"and",
include_protocols,
include_transports,
include_securities,
include_additional_matches
);
let exclude_outbounds = urltest_matching_candidate_outbounds(
urltest_candidate_tags,
names,
countries,
exclude_names,
exclude_regex,
exclude_countries,
metadata,
exclude_proxy_parameters,
"or",
exclude_protocols,
exclude_transports,
exclude_securities,
exclude_additional_matches
);
if (filter_mode == "include")
return include_outbounds;
if (filter_mode == "exclude")
return urltest_exclude_outbounds(all_outbounds, exclude_outbounds);
if (filter_mode == "mixed")
return urltest_exclude_outbounds(include_outbounds, exclude_outbounds);
return all_outbounds;
}
function urltest_filtered_outbounds(section, urltest_id, urltest_candidate_tags, state) {
return filter_candidate_outbounds(
connections.urltest_filter_mode(section, urltest_id),
urltest_candidate_tags,
object_or_empty(object_or_empty(state.outboundMetadata).names),
urltest_country_metadata(section, urltest_id, state),
object_or_empty(state.outboundMetadata),
connections.urltest_include_outbounds(section, urltest_id),
connections.urltest_include_regex(section, urltest_id),
connections.urltest_include_countries(section, urltest_id),
connections.urltest_include_proxy_parameters(section, urltest_id),
connections.urltest_include_protocols(section, urltest_id),
connections.urltest_include_transports(section, urltest_id),
connections.urltest_include_securities(section, urltest_id),
connections.urltest_exclude_outbounds(section, urltest_id),
connections.urltest_exclude_regex(section, urltest_id),
connections.urltest_exclude_countries(section, urltest_id),
connections.urltest_exclude_proxy_parameters(section, urltest_id),
connections.urltest_exclude_protocols(section, urltest_id),
connections.urltest_exclude_transports(section, urltest_id),
connections.urltest_exclude_securities(section, urltest_id)
);
}
function priority_level_country_metadata(group_id, level_id, state) {
let metadata = object_or_empty(object_or_empty(state.outboundMetadata).countries);
let detect_method = connections.priority_level_detect_server_country(group_id, level_id);
if (detect_method == "flag_emoji")
return runtime_urltest.countries_from_flag_names(object_or_empty(object_or_empty(state.outboundMetadata).names));
return metadata;
}
function priority_level_filtered_outbounds(group_id, level_id, urltest_candidate_tags, state) {
if (connections.priority_level_direct(group_id, level_id))
return [ runtime_constants.DIRECT_OUTBOUND_TAG ];
return filter_candidate_outbounds(
connections.priority_level_filter_mode(group_id, level_id),
urltest_candidate_tags,
object_or_empty(object_or_empty(state.outboundMetadata).names),
priority_level_country_metadata(group_id, level_id, state),
object_or_empty(state.outboundMetadata),
connections.priority_level_include_outbounds(group_id, level_id),
connections.priority_level_include_regex(group_id, level_id),
connections.priority_level_include_countries(group_id, level_id),
connections.priority_level_include_proxy_parameters(group_id, level_id),
connections.priority_level_include_protocols(group_id, level_id),
connections.priority_level_include_transports(group_id, level_id),
connections.priority_level_include_securities(group_id, level_id),
connections.priority_level_exclude_outbounds(group_id, level_id),
connections.priority_level_exclude_regex(group_id, level_id),
connections.priority_level_exclude_countries(group_id, level_id),
connections.priority_level_exclude_proxy_parameters(group_id, level_id),
connections.priority_level_exclude_protocols(group_id, level_id),
connections.priority_level_exclude_transports(group_id, level_id),
connections.priority_level_exclude_securities(group_id, level_id)
);
}
function dashboard_country_metadata(section, state) {
let metadata = object_or_empty(object_or_empty(state.outboundMetadata).countries);
if (connections.dashboard_detect_server_country(section) == "flag_emoji")
return runtime_urltest.countries_from_flag_names(object_or_empty(object_or_empty(state.outboundMetadata).names));
return metadata;
}
function selected_group_outbounds(group_names, group_outbounds) {
group_outbounds = object_or_empty(group_outbounds);
let result = [];
for (let group_name in array_or_empty(group_names))
for (let tag_name in array_or_empty(group_outbounds[group_name]))
push(result, tag_name);
return unique_string_array(result);
}
function remember_dashboard_group_outbounds(group_outbounds, group_name, outbounds) {
group_name = as_string(group_name);
if (group_name == "")
return;
let combined = array_or_empty(group_outbounds[group_name]);
for (let tag_name in array_or_empty(outbounds))
push(combined, tag_name);
group_outbounds[group_name] = unique_string_array(combined);
}
function dashboard_filtered_outbounds(section, selector_tags, state, group_outbounds) {
return filter_candidate_outbounds(
connections.dashboard_filter_mode(section),
selector_tags,
object_or_empty(object_or_empty(state.outboundMetadata).names),
dashboard_country_metadata(section, state),
object_or_empty(state.outboundMetadata),
connections.dashboard_include_outbounds(section),
connections.dashboard_include_regex(section),
connections.dashboard_include_countries(section),
connections.dashboard_include_proxy_parameters(section),
connections.dashboard_include_protocols(section),
connections.dashboard_include_transports(section),
connections.dashboard_include_securities(section),
connections.dashboard_exclude_outbounds(section),
connections.dashboard_exclude_regex(section),
connections.dashboard_exclude_countries(section),
connections.dashboard_exclude_proxy_parameters(section),
connections.dashboard_exclude_protocols(section),
connections.dashboard_exclude_transports(section),
connections.dashboard_exclude_securities(section),
selected_group_outbounds(connections.dashboard_include_groups(section), group_outbounds),
selected_group_outbounds(connections.dashboard_exclude_groups(section), group_outbounds)
);
}
function priority_levels_with_outbounds(group_id, urltest_candidate_tags, state) {
let result = [];
let assigned = {};
for (let level_id in connections.priority_levels(group_id)) {
let outbounds = [];
for (let tag_name in priority_level_filtered_outbounds(group_id, level_id, urltest_candidate_tags, state)) {
if (!assigned[tag_name]) {
assigned[tag_name] = true;
push(outbounds, tag_name);
}
}
push(result, {
id: level_id,
displayName: connections.priority_level_display_name(group_id, level_id),
order: int(connections.priority_level_order(group_id, level_id), 10),
direct: connections.priority_level_direct(group_id, level_id),
filter_mode: connections.priority_level_filter_mode(group_id, level_id),
detect_server_country: connections.priority_level_detect_server_country(group_id, level_id),
outbounds
});
}
return result;
}
function priority_group_outbounds(levels) {
let result = [];
let seen = {};
for (let level in array_or_empty(levels)) {
for (let tag_name in array_or_empty(level.outbounds)) {
tag_name = as_string(tag_name);
if (tag_name != "" && !seen[tag_name]) {
seen[tag_name] = true;
push(result, tag_name);
}
}
}
return result;
}
function urltest_outbound_tag(section_name, urltest_id) {
urltest_id = as_string(urltest_id);
return urltest_id == "urltest"
? outbound_tag(section_name + "-urltest")
: outbound_tag(section_name + "-urltest-" + urltest_id);
}
function priority_outbound_tag(section_name, group_id) {
return outbound_tag(section_name + "-priority-" + as_string(group_id));
}
function add_urltest_outbound(config, section, urltest_id, urltest_candidate_tags, state) {
let section_name = section[".name"];
let urltest_outbounds = urltest_filtered_outbounds(section, urltest_id, urltest_candidate_tags, state);
let urltest_tag = urltest_outbound_tag(section_name, urltest_id);
let display_name = connections.urltest_display_name(section, urltest_id);
let urltest_outbound = {
type: "urltest",
tag: urltest_tag,
outbounds: urltest_outbounds,
url: connections.urltest_testing_url(section, urltest_id),
interval: urltest_check_interval(section, urltest_id),
tolerance: int(connections.urltest_tolerance(section, urltest_id), 10),
interrupt_exist_connections: connections.urltest_interrupt_exist_connections(section, urltest_id)
};
let idle_timeout = urltest_idle_timeout(section, urltest_id);
if (idle_timeout != "")
urltest_outbound.idle_timeout = idle_timeout;
runtime_subscription.remember_outbound_metadata(state, urltest_tag, display_name, urltest_outbound);
runtime_subscription.remember_urltest_group_config(state, urltest_tag, {
displayName: display_name,
outbounds: urltest_outbounds,
url: urltest_outbound.url,
interval: urltest_outbound.interval,
tolerance: urltest_outbound.tolerance,
idle_timeout: urltest_outbound.idle_timeout,
interrupt_exist_connections: urltest_outbound.interrupt_exist_connections
});
if (length(urltest_outbounds) == 0)
return {
tag: "",
outbounds: []
};
push(config.outbounds, urltest_outbound);
return {
tag: urltest_tag,
outbounds: urltest_outbounds
};
}
function add_priority_group_outbound(config, section, group_id, urltest_candidate_tags, state) {
let section_name = section[".name"];
let levels = priority_levels_with_outbounds(group_id, urltest_candidate_tags, state);
let outbounds = priority_group_outbounds(levels);
let priority_tag = priority_outbound_tag(section_name, group_id);
let display_name = connections.priority_group_display_name(section, group_id);
let outbound = {
type: "selector",
tag: priority_tag,
outbounds,
default: outbounds[0],
interrupt_exist_connections: connections.priority_group_interrupt_exist_connections(section, group_id)
};
runtime_subscription.remember_outbound_metadata(state, priority_tag, display_name, outbound);
runtime_subscription.remember_priority_group(state, priority_tag, {
id: group_id,
tag: priority_tag,
section: section_name,
displayName: display_name,
health_url: connections.priority_group_health_url(section, group_id),
active_check_interval: connections.priority_group_active_check_interval(section, group_id),
check_timeout: connections.priority_group_check_timeout(section, group_id),
recovery_check_interval: connections.priority_group_recovery_check_interval(section, group_id),
pick_fastest: connections.priority_group_pick_fastest(section, group_id),
switch_to_faster_same_priority: connections.priority_group_switch_to_faster_same_priority(section, group_id),
fastest_check_interval: connections.priority_group_fastest_check_interval(section, group_id),
interrupt_exist_connections: connections.priority_group_interrupt_exist_connections(section, group_id),
pin_dashboard: connections.priority_group_pin_dashboard(section, group_id),
outbounds,
levels
});
if (length(outbounds) == 0)
return {
tag: "",
outbounds: []
};
push(config.outbounds, outbound);
return {
tag: priority_tag,
outbounds
};
}
function add_proxy_selector(config, section, selector_tags, urltest_candidate_tags, state) {
let section_name = section[".name"];
let selector_tag = outbound_tag(section_name);
let selector_outbounds = selector_tags;
let selector_default = selector_tags[0];
let urltest_tags = [];
let priority_tags = [];
let group_outbounds = {};
for (let urltest_id in connections.urltests(section)) {
let urltest = add_urltest_outbound(config, section, urltest_id, urltest_candidate_tags, state);
remember_dashboard_group_outbounds(
group_outbounds,
connections.urltest_display_name(section, urltest_id),
urltest.outbounds
);
if (urltest.tag == "")
continue;
push(urltest_tags, urltest.tag);
}
for (let group_id in connections.priority_groups(section)) {
let priority = add_priority_group_outbound(config, section, group_id, urltest_candidate_tags, state);
remember_dashboard_group_outbounds(
group_outbounds,
connections.priority_group_display_name(section, group_id),
priority.outbounds
);
if (priority.tag == "")
continue;
push(priority_tags, priority.tag);
}
selector_outbounds = dashboard_filtered_outbounds(section, selector_tags, state, group_outbounds);
selector_default = selector_outbounds[0];
if (length(urltest_tags) > 0 || length(priority_tags) > 0) {
for (let tag in urltest_tags)
push(selector_outbounds, tag);
for (let tag in priority_tags)
push(selector_outbounds, tag);
selector_default = length(urltest_tags) > 0 ? urltest_tags[0] : priority_tags[0];
}
if (length(selector_outbounds) == 0)
ctx.runtime_generate_unsupported("dashboard server filtering produced no usable outbounds");
push(config.outbounds, {
type: "selector",
tag: selector_tag,
outbounds: selector_outbounds,
default: selector_default,
interrupt_exist_connections: true
});
}
function ensure_custom_ruleset(config, reference) {
let tag_name;
let kind = runtime_rulesets.kind_from_reference_hint(reference);
if (runtime_rulesets.is_community(reference)) {
tag_name = "builtin-" + reference + "-ruleset";
kind = "domains";
if (!ruleset_registered(config, tag_name)) {
let rule_set = {
type: "remote",
tag: tag_name,
format: "binary",
url: runtime_rulesets.community_url(reference)
};
let detour = ctx.download_detour_tag(ctx.runtime_settings());
if (detour != "")
rule_set.download_detour = detour;
rule_set.update_interval = remote_ruleset_update_interval();
push(config.route.rule_set, rule_set);
}
return { tag: tag_name, kind };
}
tag_name = "inline-custom-" + runtime_rulesets.hash12(reference) + "-ruleset";
if (kind == "unknown")
kind = "domains";
if (ruleset_registered(config, tag_name))
return { tag: tag_name, kind };
let extension = runtime_rulesets.file_extension(reference);
if (substr(reference, 0, 1) == "/") {
if (extension != "srs" && extension != "json")
ctx.runtime_generate_unsupported("local rule_set extension is not supported by sing-box config generation");
// Skip broken local ruleset files — nonexistent or suspiciously small
if (!helpers.file_is_usable(reference, 100))
return null;
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: extension == "json" ? "source" : "binary",
path: reference
});
}
else if (substr(reference, 0, 7) == "http://" || substr(reference, 0, 8) == "https://") {
let rule_set = {
type: "remote",
tag: tag_name,
format: runtime_rulesets.remote_format(reference),
url: reference
};
let detour = ctx.download_detour_tag(ctx.runtime_settings());
if (detour != "")
rule_set.download_detour = detour;
rule_set.update_interval = remote_ruleset_update_interval();
push(config.route.rule_set, rule_set);
}
else {
ctx.runtime_generate_unsupported("rule_set reference is not supported by sing-box config generation");
}
return { tag: tag_name, kind };
}
function ensure_community_ruleset(config, section_name, community) {
if (!runtime_rulesets.is_community(community))
ctx.runtime_generate_unsupported("unknown community list " + community);
let tag_name = ruleset_tag(section_name, community, "community");
if (!ruleset_registered(config, tag_name)) {
let folder = ctx.runtime_ruleset_folder || runtime_ruleset_folder;
let tmp_srs = folder + "/community-" + community + ".srs";
let etc_srs = "/etc/tachyon/rulesets/community-" + community + ".srs";
let local_path = null;
if (helpers.file_is_usable(tmp_srs, 100))
local_path = tmp_srs;
else if (helpers.file_is_usable(etc_srs, 100))
local_path = etc_srs;
if (local_path != null) {
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: "binary",
path: local_path
});
}
else {
let rule_set = {
type: "remote",
tag: tag_name,
format: "binary",
url: runtime_rulesets.community_url(community),
update_interval: remote_ruleset_update_interval()
};
let detour = ctx.download_detour_tag(ctx.runtime_settings(), "lists");
if (detour != "")
rule_set.download_detour = detour;
push(config.route.rule_set, rule_set);
}
}
return {
tag: tag_name,
kind: "domains"
};
}
function domain_ip_list_ruleset_tag(section_name) {
return ruleset_tag(section_name, "lists", "");
}
function domain_ip_list_ruleset_path(section_name) {
let folder = ctx.runtime_ruleset_folder || runtime_ruleset_folder;
return folder + "/" + domain_ip_list_ruleset_tag(section_name) + ".json";
}
function reference_is_local(reference) {
return substr(as_string(reference), 0, 1) == "/";
}
function source_file_exists(path) {
return fs.readfile(path) != null;
}
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;
for (let reference in references) {
if (reference_is_local(reference)) {
has_local = true;
break;
}
}
if (!has_local)
return;
fs.unlink(ruleset_path);
source_rulesets.create_source(ruleset_path);
for (let reference in references) {
reference = as_string(reference);
if (!reference_is_local(reference))
continue;
if (!source_file_exists(reference)) {
warn("local domain/IP list not found: ", reference, "\n");
continue;
}
source_rulesets.import_plain_list(reference, ruleset_path, "domain_suffix", "domains", "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, domains_only) {
if (length(references) == 0)
return;
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))
return;
let tag_name = domain_ip_list_ruleset_tag(section_name);
if (!ruleset_registered(config, tag_name)) {
push(config.route.rule_set, {
type: "local",
tag: tag_name,
format: "source",
path: ruleset_path
});
}
if (!domains_only)
push(rule_set_tags, tag_name);
if (source_rulesets.has_domain_matchers(ruleset_path))
push(dns_rule_set_tags, tag_name);
}
function legacy_condition_values(section, key) {
let raw_values = object_or_empty(section)[key];
let list_values = type(raw_values) == "array"
? raw_values
: [];
let option_text_values = type(raw_values) == "array" || key == "domain"
? []
: rule_config.text_list_values(raw_values, "comma-space");
let text_value = option(section, key + "_text", "");
let text_values = rule_config.text_list_values(text_value, "comma-space");
if (bool_option(section, key + "_text_mode", false) || bool_option(section, "conditions_text_mode", false))
return text_values;
if (length(list_values) > 0)
return list_values;
if (length(option_text_values) > 0)
return option_text_values;
return text_values;
}
function combined_domain_source_values(section) {
let values = [];
if (type(object_or_empty(section)["domain"]) != "array") {
for (let value in rule_config.text_list_values(option(section, "domain", ""), "comma-space"))
if (as_string(value) != "")
push(values, as_string(value));
}
for (let value in rule_config.text_list_values(option(section, "domain_suffix_text", ""), "comma-space"))
if (as_string(value) != "")
push(values, as_string(value));
for (let value in list_option(section, "domain_suffix"))
if (as_string(value) != "")
push(values, as_string(value));
for (let value in list_option(section, "user_domains"))
if (as_string(value) != "")
push(values, as_string(value));
for (let value in rule_config.text_list_values(option(section, "user_domains_text", ""), "comma-space"))
if (as_string(value) != "")
push(values, as_string(value));
return values;
}
function domain_suffix_condition_value_kind(value) {
return rule_config.prefixed_domain_kind_value(value);
}
function domain_conditions(section) {
let result = {
domain: [],
domain_suffix: [],
domain_keyword: [],
domain_regex: []
};
for (let key in [ "domain", "domain_keyword", "domain_regex" ]) {
for (let value in legacy_condition_values(section, key)) {
let normalized = rule_config.domain_value_for_key(value, key);
if (normalized != null)
push(result[key], normalized);
}
}
for (let value in combined_domain_source_values(section)) {
let normalized = domain_suffix_condition_value_kind(value);
if (normalized != null)
push(result[normalized.kind], normalized.value);
}
return result;
}
function add_domain_array(rule, key, values) {
if (length(values) > 0)
rule[key] = values;
}
function push_dns_matcher_rule(config, rule) {
push(config.dns.rules, rule);
}
function section_dns_server(section) {
return option(section, "action", "") == "bypass"
? runtime_constants.DNS_SERVER_TAG
: runtime_constants.FAKEIP_DNS_SERVER_TAG;
}
function single_or_array(values) {
return length(values) == 1 ? values[0] : values;
}
function dns_action_server_tag(section_name, idx) {
if (idx == null)
return runtime_constants.tag(section_name, "dns-server");
return runtime_constants.tag(section_name, "dns-server-" + idx);
}
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 section_dns_servers(section) {
let servers = list_option(section, "dns_server");
if (length(servers) == 0) {
let single = option(section, "dns_server", "");
servers = [single];
}
return servers;
}
function add_dns_server_for_section(config, section) {
let section_name = section[".name"];
let servers = section_dns_servers(section);
let dns_type = option(section, "dns_type", "udp");
let detour = dns_action_detour_tag(section);
let server_tags = [];
for (let i = 0; i < length(servers); i++) {
let s_val = servers[i];
let tag_name = length(servers) <= 1
? dns_action_server_tag(section_name)
: dns_action_server_tag(section_name, i + 1);
let server = runtime_dns.server_from_options(
tag_name,
dns_type,
s_val,
detour
);
if (server.unsupported)
ctx.runtime_generate_unsupported(server.unsupported);
push(config.dns.servers, server);
push(server_tags, tag_name);
}
return server_tags;
}
function add_dns_action_rules_for_section(config, section) {
let domains = domain_conditions(section);
let domain = domains.domain;
let domain_suffix = domains.domain_suffix;
let domain_keyword = domains.domain_keyword;
let domain_regex = domains.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 != null && 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(ctx.runtime_settings(), "dns_rewrite_ttl", "60");
let section_servers = section_dns_servers(section);
let server_tags = [];
if (length(section_servers) <= 1) {
push(server_tags, dns_action_server_tag(section_name));
} else {
for (let i = 0; i < length(section_servers); i++)
push(server_tags, dns_action_server_tag(section_name, i + 1));
}
let server_tag = single_or_array(server_tags);
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)
ctx.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);
}
function add_dscp_matchers(rule, section) {
let dscp_vals = connections.dscp_list(section);
if (length(dscp_vals) > 0)
rule.dscp = dscp_vals;
}
function add_port_matchers(rule, section) {
let values = [];
for (let value in list_option(section, "ports"))
push(values, value);
for (let value in rule_config.text_list_values(option(section, "ports_text", ""), "comma-space"))
push(values, value);
let ports = [];
let port_ranges = [];
let seen = {};
for (let value in values) {
value = trim(as_string(value));
if (value == "" || seen[value])
continue;
seen[value] = true;
let dash = index(value, "-");
if (dash < 0) {
let port = normalize_port_number_value(value);
if (port != null)
push(ports, port);
continue;
}
let start = normalize_port_number_value(substr(value, 0, dash));
let end = normalize_port_number_value(substr(value, dash + 1));
if (start != null && end != null && start <= end)
push(port_ranges, start == end ? as_string(start) : sprintf("%d:%d", start, end));
}
if (length(ports) > 0)
rule.port = ports;
if (length(port_ranges) > 0)
rule.port_range = port_ranges;
}
function tproxy_inbound_matcher() {
return [ runtime_constants.TPROXY_INBOUND_TAG, runtime_constants.TPROXY_INBOUND6_TAG ];
}
function add_fully_routed_ips_rule(config, section) {
let source_ip_cidr = list_option(section, "fully_routed_ips");
if (length(source_ip_cidr) == 0)
return;
let target = runtime_route.target(section, outbound_tag(section[".name"]));
if (target.unsupported)
ctx.runtime_generate_unsupported(target.unsupported);
let route_rule = {
action: target.action,
inbound: tproxy_inbound_matcher()
};
if (target.outbound)
route_rule.outbound = target.outbound;
route_rule.source_ip_cidr = single_or_array(source_ip_cidr);
push(config.route.rules, route_rule);
}
function add_excluded_ips_rule(config, section) {
let excluded = list_option(section, "excluded_ips");
if (length(excluded) == 0)
return;
let route_rule = {
action: "route",
inbound: tproxy_inbound_matcher(),
outbound: runtime_constants.DIRECT_OUTBOUND_TAG,
source_ip_cidr: single_or_array(excluded)
};
push(config.route.rules, route_rule);
}
function add_excluded_protocol_rule(config, section) {
let excluded = list_option(section, "excluded_protocol");
if (length(excluded) == 0)
return;
let route_rule = {
action: "route",
inbound: tproxy_inbound_matcher(),
outbound: runtime_constants.DIRECT_OUTBOUND_TAG,
protocol: single_or_array(excluded)
};
push(config.route.rules, route_rule);
}
function add_protocol_matchers(rule, section) {
let protocols = list_option(section, "protocol");
if (length(protocols) > 0)
rule.protocol = single_or_array(protocols);
}
function load_community_subnet_cidrs(community) {
let paths = [
"/tmp/sing-box/rulesets/community-subnets-" + as_string(community) + ".lst",
"/etc/tachyon/rulesets/community-subnets-" + as_string(community) + ".lst",
(ctx.runtime_ruleset_folder || runtime_ruleset_folder || "/tmp/sing-box/rulesets") + "/community-subnets-" + as_string(community) + ".lst"
];
for (let path in paths) {
let content = fs.readfile(path);
if (content != null && content != "") {
let cidrs = [];
for (let line in split(as_string(content), "\n")) {
line = trim(replace(as_string(line), /\r/g, ""));
if (line != "" && substr(line, 0, 1) != "#")
push(cidrs, line);
}
if (length(cidrs) > 0)
return cidrs;
}
}
return [];
}
function add_combined_route_for_section(config, section) {
let domains = domain_conditions(section);
let domain = domains.domain;
let domain_suffix = domains.domain_suffix;
let domain_keyword = domains.domain_keyword;
let domain_regex = domains.domain_regex;
let ip_cidr = legacy_condition_values(section, "ip_cidr");
let source_ip_cidr = legacy_condition_values(section, "source_ip_cidr");
let rule_set_tags = [];
let dns_rule_set_tags = [];
let section_name = section[".name"];
add_excluded_ips_rule(config, section);
add_excluded_protocol_rule(config, section);
add_fully_routed_ips_rule(config, section);
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);
push(dns_rule_set_tags, ensured.tag);
for (let cidr in load_community_subnet_cidrs(community))
push(ip_cidr, cidr);
}
for (let reference in connections.rule_sets(section)) {
let ensured = ensure_custom_ruleset(config, as_string(reference));
if (ensured == null)
continue;
push(rule_set_tags, ensured.tag);
if (ensured.kind == "domains")
push(dns_rule_set_tags, ensured.tag);
}
for (let reference in connections.rule_sets_with_subnets(section)) {
let ensured = ensure_custom_ruleset(config, as_string(reference));
if (ensured == null)
continue;
push(rule_set_tags, ensured.tag);
if (ensured.kind == "domains")
push(dns_rule_set_tags, ensured.tag);
}
add_domain_ip_list_ruleset(
config,
section_name,
rule_set_tags,
dns_rule_set_tags,
list_option(section, "domain_ip_lists"),
false
);
let target = runtime_route.target(section, outbound_tag(section_name));
if (target.unsupported)
ctx.runtime_generate_unsupported(target.unsupported);
let route_rule = {
action: target.action,
inbound: tproxy_inbound_matcher()
};
if (target.outbound)
route_rule.outbound = target.outbound;
add_domain_array(route_rule, "domain", domain);
add_domain_array(route_rule, "domain_suffix", domain_suffix);
add_domain_array(route_rule, "domain_keyword", domain_keyword);
add_domain_array(route_rule, "domain_regex", domain_regex);
if (length(ip_cidr) > 0)
route_rule.ip_cidr = ip_cidr;
if (length(source_ip_cidr) > 0)
route_rule.source_ip_cidr = source_ip_cidr;
add_port_matchers(route_rule, section);
add_dscp_matchers(route_rule, section);
add_protocol_matchers(route_rule, section);
if (length(rule_set_tags) > 0)
route_rule.rule_set = single_or_array(rule_set_tags);
let country_list = connections.geoip_country_list(section);
let country_mode = connections.geoip_country_mode(section);
if (target.outbound && length(country_list) > 0) {
let rule_set_tags = [];
for (let cc in country_list) {
let ip_ruleset = ensure_community_ruleset(config, section_name, "geoip_" + cc);
if (ip_ruleset && ip_ruleset.tag)
push(rule_set_tags, ip_ruleset.tag);
if (cc == "ru") {
let site_ruleset = ensure_community_ruleset(config, section_name, "geosite_ru");
if (site_ruleset && site_ruleset.tag)
push(rule_set_tags, site_ruleset.tag);
}
}
if (length(rule_set_tags) > 0) {
let geoip_route_rule = {
action: target.action,
inbound: tproxy_inbound_matcher(),
rule_set: rule_set_tags,
outbound: target.outbound
};
if (country_mode == "exclude")
geoip_route_rule.invert = true;
push(config.route.rules, geoip_route_rule);
}
}
let has_route_matchers = route_rule.domain != null || route_rule.domain_suffix != null ||
route_rule.domain_keyword != null || route_rule.domain_regex != null ||
route_rule.ip_cidr != null || route_rule.port != null || route_rule.port_range != null ||
route_rule.rule_set != null;
if (has_route_matchers) {
let resolve = runtime_route.resolve_rule_for_section(section, route_rule);
if (type(resolve) == "object" && resolve.warning)
warn(resolve.warning, "\n");
else if (type(resolve) == "object" && resolve.rule)
push(config.route.rules, resolve.rule);
push(config.route.rules, route_rule);
}
let rewrite_ttl = int_option(ctx.runtime_settings(), "dns_rewrite_ttl", "60");
if (length(domain) > 0 || length(domain_suffix) > 0 || length(domain_keyword) > 0 || length(domain_regex) > 0) {
let dns_rule = {
action: "route",
server: section_dns_server(section),
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(dns_rule_set_tags) > 0) {
push_dns_matcher_rule(config, {
action: "route",
server: section_dns_server(section),
rewrite_ttl,
rule_set: single_or_array(dns_rule_set_tags)
});
}
}
function unsupported_matcher_key(section) {
let unsupported_options = [
"subnet", "subnet_text",
"local_domain_lists", "local_subnet_lists",
"remote_domain_lists", "remote_subnet_lists"
];
for (let key in unsupported_options) {
if (length(list_option(section, key)) > 0 || option(section, key, "") != "")
return key;
}
return "";
}
function add_outbound_for_section(config, section, taken, sections) {
let action = option(section, "action", "");
let section_name = section[".name"];
if (!valid_section_name(section_name))
ctx.runtime_generate_unsupported("section name is not safe for sing-box config generation");
let unsupported_matcher = unsupported_matcher_key(section);
if (unsupported_matcher != "")
ctx.runtime_generate_unsupported("section has unsupported matcher " + unsupported_matcher);
if (connections.is_connections_action(action))
ctx.outbounds.add_connections_outbound(config, section, taken);
else if (action == "awg")
ctx.outbounds.add_awg_endpoint(config, section);
else if (action == "warp")
ctx.outbounds.add_warp_endpoint(config, section);
else if (action == "anytls")
ctx.outbounds.add_anytls_outbound(config, section);
else if (action == "snell")
ctx.outbounds.add_snell_outbound(config, section);
else if (action == "mieru")
ctx.outbounds.add_mieru_outbound(config, section);
else if (action == "sudoku")
ctx.outbounds.add_sudoku_outbound(config, section);
else if (action == "masque")
ctx.outbounds.add_masque_endpoint(config, section);
else if (action == "openvpn")
ctx.outbounds.add_openvpn_endpoint(config, section);
else if (action == "zapret")
ctx.outbounds.add_zapret_outbound(config, section, sections);
else if (action == "zapret2")
ctx.outbounds.add_zapret2_outbound(config, section, sections);
else if (action == "byedpi")
ctx.outbounds.add_byedpi_outbound(config, section, sections);
else if (action == "bypass") {
/* route-only action */
}
else if (action == "block") {
/* route-only action */
}
else if (action == "dns") {
add_dns_server_for_section(config, section);
}
else if (action == "hosts") {
/* hosts-only action — no outbound or route needed */
}
else {
ctx.runtime_generate_unsupported("unsupported action " + action);
}
}
function reserve_section_outbound_tags(sections, taken) {
for (let section in sections) {
let action = option(section, "action", "");
if (connections.is_connections_action(action) ||
action == "awg" || action == "warp" || action == "byedpi" || action == "zapret" || action == "zapret2" ||
action == "anytls" || action == "snell" || action == "mieru" || action == "sudoku" ||
action == "masque" || action == "openvpn")
taken[outbound_tag(section[".name"])] = true;
if (!connections.is_connections_action(action))
continue;
for (let urltest_id in connections.urltests(section))
taken[urltest_outbound_tag(section[".name"], urltest_id)] = true;
for (let group_id in connections.priority_groups(section))
taken[priority_outbound_tag(section[".name"], group_id)] = true;
}
}
function add_route_for_section(config, section) {
let action = option(section, "action", "");
if (action == "dns")
add_dns_action_rules_for_section(config, section);
else if (action == "hosts") {
/* hosts-only action — entries are consumed globally via combined cache */
}
else
add_combined_route_for_section(config, section);
}
function add_service_route_rules(config, sections) {
let first = null;
for (let section in sections) {
let action = option(section, "action", "");
if (connections.is_connections_action(action) ||
action == "awg" || action == "warp" || action == "byedpi" || action == "zapret" || action == "zapret2" ||
action == "anytls" || action == "snell" || action == "mieru" || action == "sudoku" ||
action == "masque" || action == "openvpn") {
first = section;
break;
}
}
if (first != null) {
push(config.route.rules, {
action: "resolve",
inbound: tproxy_inbound_matcher(),
server: runtime_constants.DNS_SERVER_TAG,
domain: runtime_constants.CHECK_PROXY_IP_DOMAIN
});
push(config.route.rules, {
action: "route",
inbound: tproxy_inbound_matcher(),
outbound: outbound_tag(first[".name"]),
domain: runtime_constants.CHECK_PROXY_IP_DOMAIN
});
}
push(config.route.rules, {
action: "route-options",
domain: runtime_constants.FAKEIP_TEST_DOMAIN,
override_port: 8443
});
}
function enabled_sections() {
let result = [];
ctx.uci_cursor().foreach(CONFIG_NAME, "section", function(section) {
if (section_enabled(section))
push(result, section);
});
return result;
}
function enabled_servers() {
let result = [];
ctx.uci_cursor().foreach(CONFIG_NAME, "server", function(section) {
if (section_enabled(section))
push(result, section);
});
return result;
}
function section_by_name(sections, name) {
name = as_string(name);
for (let section in sections)
if (as_string(section[".name"]) == name)
return section;
return null;
}
function add_server_routes(config, servers, sections) {
for (let server in servers) {
runtime_servers.add_sniff_rule(config, server);
let inbound = runtime_constants.server_inbound_tag(server[".name"]);
let routing_mode = option(server, "routing_mode", "rules");
if (routing_mode == "rules") {
runtime_servers.clone_rules_for_inbound(
config,
runtime_constants.TPROXY_INBOUND_TAG,
inbound,
runtime_constants.CHECK_PROXY_IP_DOMAIN
);
}
else if (routing_mode == "direct") {
push(config.route.rules, {
action: "route",
inbound,
outbound: runtime_constants.DIRECT_OUTBOUND_TAG
});
}
else if (routing_mode == "section") {
let routing_section_name = option(server, "routing_section", "");
let routing_section = section_by_name(sections, routing_section_name);
if (routing_section == null)
ctx.runtime_generate_unsupported("server references missing routing section " + routing_section_name);
let action = option(routing_section, "action", "");
if (action == "bypass" || action == "block")
ctx.runtime_generate_unsupported("server routing section " + routing_section_name + " cannot use action " + action);
let target = runtime_route.target(routing_section, outbound_tag(routing_section[".name"]));
if (target.unsupported)
ctx.runtime_generate_unsupported(target.unsupported);
let rule = {
action: target.action,
inbound
};
if (target.outbound)
rule.outbound = target.outbound;
push(config.route.rules, rule);
}
else {
ctx.runtime_generate_unsupported("unsupported server routing_mode " + routing_mode);
}
}
}
return {
init,
ensure_custom_ruleset,
ruleset_registered,
section_needs_country_is,
section_has_direct_priority_level,
urltest_outbound_tag,
priority_outbound_tag,
add_urltest_outbound,
add_priority_group_outbound,
add_proxy_selector,
ensure_community_ruleset,
domain_ip_list_ruleset_tag,
domain_ip_list_ruleset_path,
add_domain_ip_list_ruleset,
legacy_condition_values,
domain_conditions,
add_domain_array,
push_dns_matcher_rule,
section_dns_server,
add_dns_server_for_section,
add_dns_action_rules_for_section,
add_port_matchers,
add_fully_routed_ips_rule,
add_excluded_ips_rule,
add_combined_route_for_section,
unsupported_matcher_key,
add_outbound_for_section,
reserve_section_outbound_tags,
add_route_for_section,
add_service_route_rules,
enabled_sections,
enabled_servers,
section_by_name,
add_server_routes
};