feat: add DoH blocking, subscription keyword filtering, bulk proxy text inputs, and insecure TLS flag
This commit is contained in:
@@ -48,6 +48,25 @@ const ZAPRET2_ROUTE_MARK_BASE = 0x02000000;
|
||||
const BYEDPI_LISTEN_ADDRESS = "127.0.0.1";
|
||||
const BYEDPI_PORT_BASE = 1080;
|
||||
|
||||
const DOH_BLOCK_IPV4_CIDRS = [
|
||||
"1.1.1.1/32", "1.0.0.1/32",
|
||||
"8.8.8.8/32", "8.8.4.4/32",
|
||||
"9.9.9.9/32", "9.9.9.11/32",
|
||||
"149.112.112.112/32",
|
||||
"208.67.222.222/32", "208.67.220.220/32",
|
||||
"94.140.14.14/32", "94.140.15.15/32",
|
||||
"77.88.8.8/32", "77.88.8.1/32"
|
||||
];
|
||||
|
||||
const DOH_BLOCK_IPV6_CIDRS = [
|
||||
"2606:4700:4700::1111/128", "2606:4700:4700::1001/128",
|
||||
"2001:4860:4860::8888/128", "2001:4860:4860::8844/128",
|
||||
"2620:fe::fe/128", "2620:fe::9/128",
|
||||
"2620:119:35::35/128", "2620:119:53::53/128",
|
||||
"2a10:50c0::ad1:ff/128", "2a10:50c0::ad2:ff/128",
|
||||
"2a02:6b8::feed:0ff/128", "2a02:6b8:0:1::feed:0ff/128"
|
||||
];
|
||||
|
||||
let common = require("core.common");
|
||||
let as_string = common.as_string;
|
||||
|
||||
@@ -141,6 +160,8 @@ return {
|
||||
ZAPRET2_ROUTE_MARK_BASE,
|
||||
BYEDPI_LISTEN_ADDRESS,
|
||||
BYEDPI_PORT_BASE,
|
||||
DOH_BLOCK_IPV4_CIDRS,
|
||||
DOH_BLOCK_IPV6_CIDRS,
|
||||
RESERVED_TAGS,
|
||||
tag,
|
||||
inbound_tag,
|
||||
|
||||
@@ -959,6 +959,133 @@ function add_connection_manual_links(config, state, section, taken, selector_tag
|
||||
}
|
||||
}
|
||||
|
||||
function add_connection_text_urltest(config, state, section, taken, selector_tags, urltest_candidate_tags, index_offset) {
|
||||
let section_name = section[".name"];
|
||||
if (index(connections.urltests(section), "urltest") >= 0)
|
||||
return "";
|
||||
|
||||
let text_links = connections.urltest_text_links(section);
|
||||
if (length(text_links) == 0)
|
||||
return "";
|
||||
|
||||
let text_tags = [];
|
||||
for (let i = 0; i < length(text_links); i++)
|
||||
add_manual_proxy_link(
|
||||
config,
|
||||
state,
|
||||
section_name,
|
||||
int(index_offset || 0) + i + 1,
|
||||
text_links[i],
|
||||
taken,
|
||||
selector_tags,
|
||||
text_tags
|
||||
);
|
||||
|
||||
if (length(text_tags) == 0)
|
||||
return "";
|
||||
|
||||
let urltest_tag = ctx.routes.urltest_outbound_tag(section_name, "urltest");
|
||||
let url = connections.urltest_testing_url(section, "urltest");
|
||||
let interval = connections.urltest_check_interval(section, "urltest");
|
||||
let tolerance = int(connections.urltest_tolerance(section, "urltest"), 10);
|
||||
let interrupt = connections.urltest_interrupt_exist_connections(section, "urltest");
|
||||
let outbound = {
|
||||
type: "urltest",
|
||||
tag: urltest_tag,
|
||||
outbounds: text_tags,
|
||||
url,
|
||||
interval,
|
||||
tolerance,
|
||||
interrupt_exist_connections: interrupt
|
||||
};
|
||||
runtime_subscription.remember_outbound_metadata(state, urltest_tag, "Fastest", outbound);
|
||||
runtime_subscription.remember_urltest_group_config(state, urltest_tag, {
|
||||
displayName: "Fastest",
|
||||
outbounds: text_tags,
|
||||
url,
|
||||
interval,
|
||||
tolerance,
|
||||
idle_timeout: "",
|
||||
interrupt_exist_connections: interrupt
|
||||
});
|
||||
push(config.outbounds, outbound);
|
||||
return urltest_tag;
|
||||
}
|
||||
|
||||
function subscription_keyword_fold(value) {
|
||||
value = as_string(value);
|
||||
let bytes = [];
|
||||
let i = 0;
|
||||
while (i < length(value)) {
|
||||
let b = ord(substr(value, i, 1));
|
||||
if (b >= 65 && b <= 90) {
|
||||
push(bytes, b + 32);
|
||||
i += 1;
|
||||
}
|
||||
else if (b == 0xD0 && i + 1 < length(value)) {
|
||||
let b2 = ord(substr(value, i + 1, 1));
|
||||
if (b2 >= 0x90 && b2 <= 0x9F)
|
||||
push(bytes, 0xD0, b2 + 0x20);
|
||||
else if (b2 >= 0xA0 && b2 <= 0xAF)
|
||||
push(bytes, 0xD1, b2 - 0x20);
|
||||
else if (b2 == 0x81)
|
||||
push(bytes, 0xD1, 0x91);
|
||||
else
|
||||
push(bytes, b, b2);
|
||||
i += 2;
|
||||
}
|
||||
else {
|
||||
push(bytes, b);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let result = "";
|
||||
for (let byte in bytes)
|
||||
result += chr(byte);
|
||||
return result;
|
||||
}
|
||||
|
||||
function subscription_keyword_filter(section) {
|
||||
let include = list_option(section, "subscription_filter_include_keywords");
|
||||
let exclude = list_option(section, "subscription_filter_exclude_keywords");
|
||||
if (length(include) == 0 && length(exclude) == 0)
|
||||
return null;
|
||||
|
||||
let folded_include = [];
|
||||
for (let keyword in include) {
|
||||
keyword = as_string(keyword);
|
||||
if (keyword != "")
|
||||
push(folded_include, subscription_keyword_fold(keyword));
|
||||
}
|
||||
let folded_exclude = [];
|
||||
for (let keyword in exclude) {
|
||||
keyword = as_string(keyword);
|
||||
if (keyword != "")
|
||||
push(folded_exclude, subscription_keyword_fold(keyword));
|
||||
}
|
||||
if (length(folded_include) == 0 && length(folded_exclude) == 0)
|
||||
return null;
|
||||
|
||||
return {
|
||||
include: folded_include,
|
||||
exclude: folded_exclude
|
||||
};
|
||||
}
|
||||
|
||||
function subscription_keyword_name_passes(filter, name) {
|
||||
let folded = subscription_keyword_fold(name);
|
||||
for (let keyword in filter.exclude)
|
||||
if (index(folded, keyword) >= 0)
|
||||
return false;
|
||||
if (length(filter.include) == 0)
|
||||
return true;
|
||||
for (let keyword in filter.include)
|
||||
if (index(folded, keyword) >= 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function add_subscription_source_with_state(config, section, source_index, source_entry, taken, selector_tags, urltest_candidate_tags, state, show_metadata, include_urltest_groups, hide_urltest_group_outbounds, hide_detour_outbounds, node_prefix) {
|
||||
let section_name = section[".name"];
|
||||
let source_section = runtime_subscription.source_id(section_name, source_index);
|
||||
@@ -991,6 +1118,7 @@ function add_subscription_source_with_state(config, section, source_index, sourc
|
||||
if (include_urltest_groups === false)
|
||||
hide_urltest_group_outbounds = false;
|
||||
node_prefix = trim(as_string(node_prefix));
|
||||
let keyword_filter = subscription_keyword_filter(section);
|
||||
let prepared = [];
|
||||
let display_names = [];
|
||||
let source_links = [];
|
||||
@@ -1002,6 +1130,8 @@ function add_subscription_source_with_state(config, section, source_index, sourc
|
||||
if (include_urltest_groups === false && subscription_urltest_group_outbound(outbound))
|
||||
continue;
|
||||
let display_name = as_string(outbound.remark || outbound.tag || ("server-" + (i + 1)));
|
||||
if (keyword_filter != null && !subscription_group_outbound(outbound) && !subscription_keyword_name_passes(keyword_filter, display_name))
|
||||
continue;
|
||||
let base = as_string(outbound.tag || outbound.remark || ("server-" + (i + 1)));
|
||||
if (node_prefix != "") {
|
||||
display_name = node_prefix + " " + display_name;
|
||||
@@ -1024,6 +1154,9 @@ function add_subscription_source_with_state(config, section, source_index, sourc
|
||||
push(hidden_flags, subscription_hidden_outbound(outbound, visibility_refs, hide_urltest_group_outbounds, hide_detour_outbounds));
|
||||
}
|
||||
|
||||
if (keyword_filter != null && length(prepared) == 0)
|
||||
ctx.runtime_generate_unsupported("subscription keyword filter removed all nodes for rule '" + section_name + "'");
|
||||
|
||||
if (length(keys(skipped)) > 0)
|
||||
warn("skipped unsupported subscription outbounds for rule '", section_name, "': ", subscription_skip_summary(skipped), "\n");
|
||||
|
||||
@@ -1242,7 +1375,9 @@ function add_connections_outbound(config, section, taken) {
|
||||
let state = runtime_subscription.new_section_state(section_name);
|
||||
let cascade_start = length(array_or_empty(config.outbounds));
|
||||
|
||||
let manual_count = length(connections.connection_urls(section));
|
||||
add_connection_manual_links(config, state, section, taken, selector_tags, urltest_candidate_tags);
|
||||
let text_urltest_tag = add_connection_text_urltest(config, state, section, taken, selector_tags, urltest_candidate_tags, manual_count);
|
||||
add_connection_subscriptions(config, state, section, taken, selector_tags, urltest_candidate_tags);
|
||||
|
||||
apply_section_detour_to_connection_outbounds(
|
||||
@@ -1268,7 +1403,7 @@ function add_connections_outbound(config, section, taken) {
|
||||
state.outboundMetadata.names[runtime_constants.DIRECT_OUTBOUND_TAG] = "Direct";
|
||||
|
||||
state.urltestCandidateTags = unique_string_array(urltest_candidate_tags);
|
||||
ctx.routes.add_proxy_selector(config, section, selector_tags, urltest_candidate_tags, state);
|
||||
ctx.routes.add_proxy_selector(config, section, selector_tags, urltest_candidate_tags, state, text_urltest_tag);
|
||||
if (!ctx.atomic_write_json_file(runtime_subscription.section_cache_path(section_name), state))
|
||||
ctx.runtime_generate_unsupported("failed to write section cache for " + section_name);
|
||||
}
|
||||
|
||||
@@ -694,7 +694,7 @@ function section_excluded_candidate_tags(section, candidate_tags, state) {
|
||||
);
|
||||
}
|
||||
|
||||
function add_proxy_selector(config, section, selector_tags, urltest_candidate_tags, state) {
|
||||
function add_proxy_selector(config, section, selector_tags, urltest_candidate_tags, state, text_urltest_tag) {
|
||||
let section_name = section[".name"];
|
||||
let selector_tag = outbound_tag(section_name);
|
||||
let selector_outbounds = selector_tags;
|
||||
@@ -721,6 +721,9 @@ function add_proxy_selector(config, section, selector_tags, urltest_candidate_ta
|
||||
push(urltest_tags, urltest.tag);
|
||||
}
|
||||
|
||||
if (as_string(text_urltest_tag) != "" && index(urltest_tags, as_string(text_urltest_tag)) < 0)
|
||||
push(urltest_tags, text_urltest_tag);
|
||||
|
||||
for (let group_id in connections.priority_groups(section)) {
|
||||
let priority = add_priority_group_outbound(config, section, group_id, group_candidate_tags, state);
|
||||
remember_dashboard_group_outbounds(
|
||||
|
||||
@@ -97,6 +97,17 @@ function config(settings, runtime) {
|
||||
for (let rule in p2p_direct_rules(settings))
|
||||
push(result.rules, rule);
|
||||
}
|
||||
if (bool_option(settings, "block_doh", false)) {
|
||||
if (option(settings, "dns_type", "udp") == "doh")
|
||||
warn("block_doh is enabled but upstream DNS type is 'doh'; your own DNS queries would be blocked, switch dns_type to 'udp' or 'dot'\n");
|
||||
let doh_cidrs = [];
|
||||
for (let cidr in runtime_constants.DOH_BLOCK_IPV4_CIDRS)
|
||||
push(doh_cidrs, cidr);
|
||||
for (let cidr in runtime_constants.DOH_BLOCK_IPV6_CIDRS)
|
||||
push(doh_cidrs, cidr);
|
||||
push(result.rules, { action: "reject", inbound: runtime_constants.TPROXY_INBOUND_TAG, ip_cidr: doh_cidrs });
|
||||
push(result.rules, { action: "reject", inbound: runtime_constants.TPROXY_INBOUND6_TAG, ip_cidr: doh_cidrs });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user