Honor section order and combined domains
This commit is contained in:
@@ -611,6 +611,43 @@ migrate_0_7_17_8_zapret_nfqws_default() {
|
||||
podkop_uci_set_option "$section" "nfqws_opt" "$ZAPRET_DEFAULT_NFQWS_OPT"
|
||||
}
|
||||
|
||||
podkop_uci_add_domain_values_with_prefix() {
|
||||
local section="$1"
|
||||
local option="$2"
|
||||
local prefix="$3"
|
||||
local kind="$4"
|
||||
local values value
|
||||
|
||||
values="$(get_legacy_domain_condition_commas_string "$section" "$option" "$kind")"
|
||||
[ -n "$values" ] || return 0
|
||||
|
||||
while IFS= read -r value || [ -n "$value" ]; do
|
||||
value="$(printf '%s' "$value" | trim_string)"
|
||||
[ -n "$value" ] || continue
|
||||
podkop_uci_add_list_unique "$section" "domain_suffix" "${prefix}${value}"
|
||||
done <<EOF
|
||||
$(printf '%s' "$values" | tr ',' '\n')
|
||||
EOF
|
||||
}
|
||||
|
||||
migrate_combined_domain_conditions() {
|
||||
local section="$1"
|
||||
|
||||
podkop_uci_add_domain_values_with_prefix "$section" "domain" "full:" "domains"
|
||||
podkop_uci_add_domain_values_with_prefix "$section" "domain_keyword" "keyword:" "generic"
|
||||
podkop_uci_add_domain_values_with_prefix "$section" "domain_regex" "regex:" "generic"
|
||||
|
||||
podkop_uci_delete_option "$section" "domain"
|
||||
podkop_uci_delete_option "$section" "domain_keyword"
|
||||
podkop_uci_delete_option "$section" "domain_regex"
|
||||
podkop_uci_delete_option "$section" "domain_text"
|
||||
podkop_uci_delete_option "$section" "domain_keyword_text"
|
||||
podkop_uci_delete_option "$section" "domain_regex_text"
|
||||
podkop_uci_delete_option "$section" "domain_text_mode"
|
||||
podkop_uci_delete_option "$section" "domain_keyword_text_mode"
|
||||
podkop_uci_delete_option "$section" "domain_regex_text_mode"
|
||||
}
|
||||
|
||||
migrate_0_7_17_8_rule() {
|
||||
local section="$1"
|
||||
local converted_from_rule="${2:-0}"
|
||||
@@ -835,6 +872,7 @@ migration() {
|
||||
|
||||
config_foreach migrate_0_7_17_8_rule_section "rule"
|
||||
config_foreach migrate_0_7_17_8_rule "section"
|
||||
config_foreach migrate_combined_domain_conditions "section"
|
||||
|
||||
if [ "$podkop_config_migration_changed" -eq 1 ]; then
|
||||
log "Migrated Podkop Plus UCI config"
|
||||
@@ -983,6 +1021,33 @@ validate_urltest_regex_option() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
validate_combined_domain_option() {
|
||||
local value="$1"
|
||||
local section="$2"
|
||||
|
||||
[ -n "$value" ] || return 0
|
||||
if config_validation_ucode combined-domain-valid "$value" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Invalid domain condition '$value' in rule '$section'. Use plain domains or full:, keyword:, regex: prefixes. Aborted." "fatal"
|
||||
exit 1
|
||||
}
|
||||
|
||||
validate_combined_domain_text_option() {
|
||||
local section="$1"
|
||||
local text_value
|
||||
|
||||
config_get text_value "$section" "domain_suffix_text"
|
||||
[ -n "$text_value" ] || return 0
|
||||
if config_validation_ucode combined-domain-text-valid "$text_value" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "Invalid domain conditions in rule '$section'. Use plain domains or full:, keyword:, regex: prefixes. Aborted." "fatal"
|
||||
exit 1
|
||||
}
|
||||
|
||||
ruleset_kind_from_reference_hint() {
|
||||
local reference="$1"
|
||||
|
||||
@@ -1237,6 +1302,8 @@ process_validate_rule() {
|
||||
|
||||
validate_outbound_detour_rule "$section"
|
||||
|
||||
config_list_foreach "$section" "domain_suffix" validate_combined_domain_option "$section"
|
||||
validate_combined_domain_text_option "$section"
|
||||
config_list_foreach "$section" "community_lists" validate_service
|
||||
config_list_foreach "$section" "rule_set" validate_ruleset_reference
|
||||
config_list_foreach "$section" "rule_set_with_subnets" validate_ruleset_reference
|
||||
|
||||
@@ -80,6 +80,68 @@ function regex_valid(pattern) {
|
||||
}
|
||||
}
|
||||
|
||||
function strip_list_comment(line) {
|
||||
line = replace(as_string(line), /[[:space:]]*\/\/.*$/, "");
|
||||
return replace(line, /[[:space:]]*#.*$/, "");
|
||||
}
|
||||
|
||||
function text_list_values(value) {
|
||||
let result = [];
|
||||
|
||||
for (let line in split(as_string(value), "\n")) {
|
||||
line = replace(strip_list_comment(line), /[ ,]/g, "\n");
|
||||
for (let item in split(line, "\n")) {
|
||||
item = trim(replace(item, /\r/g, ""));
|
||||
if (item != "")
|
||||
push(result, item);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function valid_domain_suffix(value) {
|
||||
value = lc(as_string(value));
|
||||
if (substr(value, 0, 1) == ".")
|
||||
value = substr(value, 1);
|
||||
|
||||
return match(value, /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/) != null;
|
||||
}
|
||||
|
||||
function combined_domain_valid(value) {
|
||||
value = trim(as_string(value));
|
||||
if (value == "")
|
||||
return true;
|
||||
|
||||
let colon = index(value, ":");
|
||||
if (colon < 0)
|
||||
return valid_domain_suffix(value);
|
||||
|
||||
let prefix = lc(substr(value, 0, colon));
|
||||
let body = substr(value, colon + 1);
|
||||
|
||||
if (body == "")
|
||||
return false;
|
||||
|
||||
if (prefix == "full")
|
||||
return valid_domain_suffix(body);
|
||||
|
||||
if (prefix == "keyword")
|
||||
return match(body, /[,[:space:]]/) == null;
|
||||
|
||||
if (prefix == "regex")
|
||||
return match(body, /[,[:space:]]/) == null && regex_valid(body);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function combined_domain_text_valid(value) {
|
||||
for (let item in text_list_values(value))
|
||||
if (!combined_domain_valid(item))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function valid_outbound() {
|
||||
let value = read_stdin_json();
|
||||
return type(value) == "object" && type(value.type) == "string";
|
||||
@@ -160,6 +222,10 @@ else if (mode == "enum-valid")
|
||||
exit(enum_valid(ARGV[1], 2) ? 0 : 1);
|
||||
else if (mode == "regex-valid")
|
||||
exit(regex_valid(ARGV[1]) ? 0 : 1);
|
||||
else if (mode == "combined-domain-valid")
|
||||
exit(combined_domain_valid(ARGV[1]) ? 0 : 1);
|
||||
else if (mode == "combined-domain-text-valid")
|
||||
exit(combined_domain_text_valid(ARGV[1]) ? 0 : 1);
|
||||
else if (mode == "valid-outbound")
|
||||
exit(valid_outbound() ? 0 : 1);
|
||||
else if (mode == "outbound-detour-supported")
|
||||
|
||||
@@ -41,6 +41,20 @@ parse_domain_or_subnet_file_to_comma_string() {
|
||||
rules_nft_runtime_ucode domain-subnet-file-csv "$filepath" "$type"
|
||||
}
|
||||
|
||||
parse_combined_domain_string_to_commas_string() {
|
||||
local string="$1"
|
||||
local type="$2"
|
||||
|
||||
rules_nft_runtime_ucode combined-domain-text-csv "$string" "$type"
|
||||
}
|
||||
|
||||
parse_combined_domain_list_to_commas_string() {
|
||||
local list_value="$1"
|
||||
local type="$2"
|
||||
|
||||
rules_nft_runtime_ucode combined-domain-csv "$(rule_list_value_to_commas_string "$list_value")" "$type"
|
||||
}
|
||||
|
||||
split_domain_or_subnet_file() {
|
||||
local filepath="$1"
|
||||
local domains_output="$2"
|
||||
@@ -53,12 +67,104 @@ rule_list_value_to_commas_string() {
|
||||
printf '%s\n' "$1" | tr ' ' ','
|
||||
}
|
||||
|
||||
get_legacy_domain_condition_commas_string() {
|
||||
local section="$1"
|
||||
local key="$2"
|
||||
local kind="$3"
|
||||
|
||||
local text_mode conditions_text_mode text_value list_value
|
||||
config_get_bool text_mode "$section" "${key}_text_mode" 0
|
||||
config_get_bool conditions_text_mode "$section" "conditions_text_mode" 0
|
||||
config_get text_value "$section" "${key}_text"
|
||||
config_get list_value "$section" "$key"
|
||||
|
||||
if [ "$text_mode" -eq 1 ] || [ "$conditions_text_mode" -eq 1 ]; then
|
||||
case "$kind" in
|
||||
domains) parse_domain_or_subnet_string_to_commas_string "$text_value" "domains" ;;
|
||||
*)
|
||||
parse_generic_string_to_commas_string "$text_value"
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "$list_value" ]; then
|
||||
rule_list_value_to_commas_string "$list_value"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "$text_value" ]; then
|
||||
case "$kind" in
|
||||
domains) parse_domain_or_subnet_string_to_commas_string "$text_value" "domains" ;;
|
||||
*)
|
||||
parse_generic_string_to_commas_string "$text_value"
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
fi
|
||||
|
||||
rule_list_value_to_commas_string "$list_value"
|
||||
}
|
||||
|
||||
get_combined_domain_condition_commas_string() {
|
||||
local section="$1"
|
||||
local requested_key="$2"
|
||||
local legacy_kind="$3"
|
||||
|
||||
local combined_text_mode conditions_text_mode combined_text_value combined_list_value
|
||||
local combined_text_values combined_list_values combined_values legacy_values
|
||||
config_get_bool combined_text_mode "$section" "domain_suffix_text_mode" 0
|
||||
config_get_bool conditions_text_mode "$section" "conditions_text_mode" 0
|
||||
config_get combined_text_value "$section" "domain_suffix_text"
|
||||
config_get combined_list_value "$section" "domain_suffix"
|
||||
|
||||
combined_text_values="$(parse_combined_domain_string_to_commas_string "$combined_text_value" "$requested_key")"
|
||||
combined_list_values="$(parse_combined_domain_list_to_commas_string "$combined_list_value" "$requested_key")"
|
||||
if [ -n "$combined_text_values" ] && [ -n "$combined_list_values" ]; then
|
||||
combined_values="${combined_text_values},${combined_list_values}"
|
||||
else
|
||||
combined_values="${combined_text_values}${combined_list_values}"
|
||||
fi
|
||||
|
||||
if [ "$requested_key" = "domain_suffix" ]; then
|
||||
printf '%s\n' "$combined_values"
|
||||
return 0
|
||||
fi
|
||||
|
||||
legacy_values="$(get_legacy_domain_condition_commas_string "$section" "$requested_key" "$legacy_kind")"
|
||||
if [ -n "$combined_values" ] && [ -n "$legacy_values" ]; then
|
||||
printf '%s,%s\n' "$combined_values" "$legacy_values"
|
||||
else
|
||||
printf '%s%s\n' "$combined_values" "$legacy_values"
|
||||
fi
|
||||
}
|
||||
|
||||
get_rule_condition_commas_string() {
|
||||
local section="$1"
|
||||
local key="$2"
|
||||
local kind="$3"
|
||||
|
||||
local text_mode conditions_text_mode text_value list_value
|
||||
|
||||
case "$key" in
|
||||
domain)
|
||||
get_combined_domain_condition_commas_string "$section" "domain" "domains"
|
||||
return 0
|
||||
;;
|
||||
domain_suffix)
|
||||
get_combined_domain_condition_commas_string "$section" "domain_suffix" "domains"
|
||||
return 0
|
||||
;;
|
||||
domain_keyword)
|
||||
get_combined_domain_condition_commas_string "$section" "domain_keyword" "generic"
|
||||
return 0
|
||||
;;
|
||||
domain_regex)
|
||||
get_combined_domain_condition_commas_string "$section" "domain_regex" "generic"
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
config_get_bool text_mode "$section" "${key}_text_mode" 0
|
||||
config_get_bool conditions_text_mode "$section" "conditions_text_mode" 0
|
||||
config_get text_value "$section" "${key}_text"
|
||||
@@ -395,6 +501,7 @@ create_nft_rules() {
|
||||
nft add chain inet "$NFT_TABLE_NAME" proxy '{ type filter hook prerouting priority -100; policy accept; }'
|
||||
|
||||
nft add rule inet "$NFT_TABLE_NAME" mangle ct status dnat return
|
||||
nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "@$NFT_LOCALV4_SET_NAME" return
|
||||
nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto tcp meta mark set "$NFT_FAKEIP_MARK" counter
|
||||
nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr "@$NFT_COMMON_SET_NAME" meta l4proto udp meta mark set "$NFT_FAKEIP_MARK" counter
|
||||
nft add rule inet "$NFT_TABLE_NAME" mangle iifname "@$NFT_INTERFACE_SET_NAME" ip daddr . tcp dport "@$NFT_IP_PORT_SET_NAME" meta mark set "$NFT_FAKEIP_MARK" counter
|
||||
|
||||
@@ -221,6 +221,51 @@ function domain_subnet_value_valid(value, kind) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
function prefixed_domain_value(value, requested_kind) {
|
||||
value = trim(as_string(value));
|
||||
requested_kind = as_string(requested_kind);
|
||||
|
||||
if (value == "")
|
||||
return null;
|
||||
|
||||
let colon = index(value, ":");
|
||||
let prefix = "";
|
||||
let body = value;
|
||||
if (colon > 0) {
|
||||
let candidate = ascii_lower(substr(value, 0, colon));
|
||||
if (candidate == "full" || candidate == "keyword" || candidate == "regex") {
|
||||
prefix = candidate;
|
||||
body = substr(value, colon + 1);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
body = trim(body);
|
||||
if (body == "")
|
||||
return null;
|
||||
|
||||
if (prefix == "") {
|
||||
if (requested_kind != "domain_suffix")
|
||||
return null;
|
||||
let normalized = ascii_lower(body);
|
||||
return valid_domain_suffix(normalized) ? normalized : null;
|
||||
}
|
||||
|
||||
if (prefix == "full") {
|
||||
if (requested_kind != "domain")
|
||||
return null;
|
||||
let normalized = ascii_lower(body);
|
||||
return valid_domain_suffix(normalized) ? normalized : null;
|
||||
}
|
||||
|
||||
if (prefix == "keyword")
|
||||
return requested_kind == "domain_keyword" ? body : null;
|
||||
|
||||
return requested_kind == "domain_regex" ? body : null;
|
||||
}
|
||||
|
||||
function normalize_domain_subnet_value(value, kind) {
|
||||
kind = as_string(kind);
|
||||
if (kind == "domains") {
|
||||
@@ -249,6 +294,30 @@ function filter_domain_subnet_values(values, kind) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function combined_domain_text_csv(value, requested_kind) {
|
||||
let result = [];
|
||||
|
||||
for (let item in text_list_values(value, "comma-space")) {
|
||||
let normalized = prefixed_domain_value(item, requested_kind);
|
||||
if (normalized != null)
|
||||
push(result, normalized);
|
||||
}
|
||||
|
||||
print_csv(result);
|
||||
}
|
||||
|
||||
function combined_domain_csv(value, requested_kind) {
|
||||
let result = [];
|
||||
|
||||
for (let item in split(as_string(value), ",")) {
|
||||
let normalized = prefixed_domain_value(item, requested_kind);
|
||||
if (normalized != null)
|
||||
push(result, normalized);
|
||||
}
|
||||
|
||||
print_csv(result);
|
||||
}
|
||||
|
||||
function domain_subnet_text_csv(value, kind) {
|
||||
print_csv(filter_domain_subnet_values(text_list_values(value, "comma-space"), kind));
|
||||
}
|
||||
@@ -650,6 +719,10 @@ else if (mode == "stdin-contains")
|
||||
exit(stdin_contains(ARGV[1]) ? 0 : 1);
|
||||
else if (mode == "domain-subnet-text-csv")
|
||||
domain_subnet_text_csv(ARGV[1], ARGV[2]);
|
||||
else if (mode == "combined-domain-text-csv")
|
||||
combined_domain_text_csv(ARGV[1], ARGV[2]);
|
||||
else if (mode == "combined-domain-csv")
|
||||
combined_domain_csv(ARGV[1], ARGV[2]);
|
||||
else if (mode == "domain-subnet-file-csv")
|
||||
domain_subnet_file_csv(ARGV[1], ARGV[2]);
|
||||
else if (mode == "split-domain-subnet-file")
|
||||
|
||||
@@ -1178,7 +1178,6 @@ sing_box_configure_route() {
|
||||
config_list_foreach "settings" "routing_excluded_ips" exclude_source_ip_from_routing_handler "$rule_tag"
|
||||
fi
|
||||
|
||||
config_foreach configure_fully_routed_rule_handler "section"
|
||||
config_foreach configure_route_rule_handler "section"
|
||||
config_foreach configure_server_route "server"
|
||||
}
|
||||
@@ -1579,6 +1578,8 @@ configure_route_rule_handler() {
|
||||
|
||||
log "Configuring route rule '$section' with action '$action'"
|
||||
|
||||
configure_fully_routed_ips_for_section "$section" "$action"
|
||||
|
||||
if ! rule_has_primary_matchers "$section"; then
|
||||
log "Rule '$section' has no destination matchers and action '$action', skipping regular route creation" "warn"
|
||||
return 0
|
||||
@@ -1661,19 +1662,6 @@ configure_route_rule_handler() {
|
||||
fi
|
||||
}
|
||||
|
||||
configure_fully_routed_rule_handler() {
|
||||
local section="$1"
|
||||
|
||||
rule_is_enabled "$section" || return 0
|
||||
subscription_section_is_deferred "$section" && return 0
|
||||
|
||||
local action
|
||||
action="$(get_rule_action "$section")"
|
||||
[ -n "$action" ] || return 0
|
||||
|
||||
configure_fully_routed_ips_for_section "$section" "$action"
|
||||
}
|
||||
|
||||
configure_fully_routed_ips_for_section() {
|
||||
local section="$1"
|
||||
local action="$2"
|
||||
|
||||
Reference in New Issue
Block a user