feat: Add local subnet lists support with UI and backend integration (#156)

This commit is contained in:
Andrey Petelin
2025-09-05 21:23:55 +05:00
parent 5273935d25
commit 49836e4adc
6 changed files with 94 additions and 31 deletions

View File

@@ -370,7 +370,7 @@ function createConfigSection(section, map, network) {
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'local_domain_lists', _('Local Domain Lists Path'), _('Enter the list file path'));
o = s.taboption('basic', form.DynamicList, 'local_domain_lists', _('Local Domain List Paths'), _('Enter the list file path'));
o.placeholder = '/path/file.lst';
o.depends('local_domain_lists_enabled', '1');
o.rmempty = false;
@@ -399,6 +399,25 @@ function createConfigSection(section, map, network) {
return validateUrl(value);
};
o = s.taboption('basic', form.Flag, 'local_subnet_lists_enabled', _('Local Subnet Lists'), _('Use the list from the router filesystem'));
o.default = '0';
o.rmempty = false;
o.ucisection = s.section;
o = s.taboption('basic', form.DynamicList, 'local_subnet_lists', _('Local Subnet List Paths'), _('Enter the list file path'));
o.placeholder = '/path/file.lst';
o.depends('local_subnet_lists_enabled', '1');
o.rmempty = false;
o.ucisection = s.section;
o.validate = function (section_id, value) {
if (!value || value.length === 0) return true;
const pathRegex = /^\/[a-zA-Z0-9_\-\/\.]+$/;
if (!pathRegex.test(value)) {
return _('Invalid path format. Path must start with "/" and contain valid characters');
}
return true;
};
o = s.taboption('basic', form.ListValue, 'user_subnet_list_type', _('User Subnet List Type'), _('Select how to add your custom subnets'));
o.value('disabled', _('Disabled'));
o.value('dynamic', _('Dynamic List'));

View File

@@ -97,8 +97,8 @@ msgstr "Локальные списки доменов"
msgid "Use the list from the router filesystem"
msgstr "Использовать список из файловой системы роутера"
msgid "Local Domain Lists Path"
msgstr "Путь к локальным спискам доменов"
msgid "Local Domain List Paths"
msgstr "Пути к локальным спискам доменов"
msgid "Enter to the list file path"
msgstr "Введите путь к файлу списка"
@@ -896,4 +896,10 @@ msgid "Delay in milliseconds before reloading podkop after interface UP"
msgstr "Задержка в миллисекундах перед перезагрузкой podkop после поднятия интерфейса"
msgid "Delay value cannot be empty"
msgstr "Значение не может быть пустым"
msgstr "Значение не может быть пустым"
msgid "Local Subnet Lists"
msgstr "Локальные списки подсетей"
msgid "Local Subnet List Paths"
msgstr "Пути к локальным спискам доменов"

View File

@@ -97,7 +97,7 @@ msgstr ""
msgid "Use the list from the router filesystem"
msgstr ""
msgid "Local Domain Lists Path"
msgid "Local Domain List Paths"
msgstr ""
msgid "Enter to the list file path"
@@ -1250,4 +1250,10 @@ msgid "Delay in milliseconds before reloading podkop after interface UP"
msgstr ""
msgid "Delay value cannot be empty"
msgstr ""
msgid "Local Subnet Lists"
msgstr ""
msgid "Local Subnet List Paths"
msgstr ""

View File

@@ -16,6 +16,8 @@ config main 'main'
option user_subnet_list_type 'disable'
#list user_subnets ''
#option user_subnets_text ''
option local_subnet_lists_enabled '0'
#list local_subnet_lists ''
option remote_subnet_lists_enabled '0'
#list remote_subnet_lists ''
option all_traffic_from_ip_enabled '0'

View File

@@ -870,13 +870,15 @@ exclude_source_ip_from_routing_handler() {
configure_routing_for_section_lists() {
local section="$1"
local community_lists_enabled local_domain_lists_enabled remote_domain_lists_enabled remote_subnet_lists_enabled
local community_lists_enabled local_domain_lists_enabled remote_domain_lists_enabled remote_subnet_lists_enabled \
local_subnet_lists_enabled
local user_domain_list_type user_subnet_list_type route_rule_tag
config_get_bool community_lists_enabled "$section" "community_lists_enabled" 0
config_get user_domain_list_type "$section" "user_domain_list_type" "disabled"
config_get_bool local_domain_lists_enabled "$section" "local_domain_lists_enabled" 0
config_get_bool remote_domain_lists_enabled "$section" "remote_domain_lists_enabled" 0
config_get user_subnet_list_type "$section" "user_subnet_list_type" "disabled"
config_get_bool local_subnet_lists_enabled "$section" "local_subnet_lists_enabled" 0
config_get_bool remote_subnet_lists_enabled "$section" "remote_subnet_lists_enabled" 0
if [ "$community_lists_enabled" -eq 0 ] && \
@@ -884,6 +886,7 @@ configure_routing_for_section_lists() {
[ "$local_domain_lists_enabled" -eq 0 ] && \
[ "$remote_domain_lists_enabled" -eq 0 ] && \
[ "$user_subnet_list_type" == "disabled" ] && \
[ "$local_subnet_lists_enabled" -eq 0 ] && \
[ "$remote_subnet_lists_enabled" == 0 ] ; then
log "Section $section does not have any enabled list, skipping..." "warn"
return 0
@@ -906,7 +909,7 @@ configure_routing_for_section_lists() {
if [ "$local_domain_lists_enabled" -eq 1 ]; then
log "Processing local domains routing rules for $section section"
configure_local_domain_lists "$section" "$route_rule_tag"
configure_local_domain_or_subnet_lists "$section" "domains" "$route_rule_tag"
fi
if [ "$remote_domain_lists_enabled" -eq 1 ]; then
@@ -921,6 +924,11 @@ configure_routing_for_section_lists() {
# configure_user_subnet_list_handler
fi
if [ "$local_subnet_lists_enabled" -eq 1 ]; then
log "Processing local subnets routing rules for $section section"
configure_local_domain_or_subnet_lists "$section" "subnets" "$route_rule_tag"
fi
if [ "$remote_subnet_lists_enabled" -eq 1 ]; then
log "Processing remote subnets routing rules for $section section"
config_list_foreach "$section" "remote_subnet_lists" configure_remote_domain_or_subnet_list_handler \
@@ -950,58 +958,81 @@ configure_user_domain_list_handler() {
# TODO(ampetelin): it is necessary to implement
}
configure_local_domain_lists() {
configure_local_domain_or_subnet_lists() {
local section="$1"
local route_rule_tag="$2"
local type="$2"
local route_rule_tag="$3"
local ruleset_tag ruleset_filename ruleset_filepath
ruleset_tag="$(get_ruleset_tag "$section" "local" "domains")"
ruleset_tag="$(get_ruleset_tag "$section" "local" "$type")"
ruleset_filename="$ruleset_tag.json"
ruleset_filepath="$TMP_FOLDER/$ruleset_filename"
sing_box_cm_create_local_source_ruleset "$ruleset_filepath"
config=$(sing_box_cm_add_local_ruleset "$config" "$ruleset_tag" "source" "$ruleset_filepath")
config=$(sing_box_cm_patch_route_rule "$config" "$route_rule_tag" "rule_set" "$ruleset_tag")
_add_ruleset_to_dns_rules "$ruleset_tag" "$route_rule_tag"
config_list_foreach "$section" "local_domains_list" import_local_domain_list_to_ruleset "$section" "$ruleset_filepath"
case "$type" in
domains)
config_list_foreach "$section" "local_domain_lists" import_local_domain_or_subnet_list_to_ruleset "$type" \
"$section" "$ruleset_filepath"
_add_ruleset_to_dns_rules "$ruleset_tag" "$route_rule_tag" ;;
subnets)
config_list_foreach "$section" "local_subnet_lists" import_local_domain_or_subnet_list_to_ruleset "$type" \
"$section" "$ruleset_filepath";;
*) log "Unsupported local rule set type: $type" "warn" ;;
esac
}
import_local_domain_list_to_ruleset() {
import_local_domain_or_subnet_list_to_ruleset() {
local filepath="$1"
local section="$2"
local ruleset_filepath="$3"
local type="$2"
local section="$3"
local ruleset_filepath="$4"
if ! file_exists "$filepath"; then
log "File $filepath not found" "warn"
return 1
fi
local domains=""
while IFS= read -r domain; do
if [ -z "$domain" ]; then
local items=""
while IFS= read -r item; do
if [ -z "$item" ]; then
continue
fi
if ! is_domain "$domain"; then
log "$domain is not domain" "debug"
continue
fi
case "$type" in
domains)
if ! is_domain "$item"; then
log "$item is not domain" "debug"
continue
fi
;;
subnets)
if ! is_ipv4 "$item" && ! is_ipv4_cidr "$item"; then
log "$item is not IPv4 IP or CIDR" "debug"
continue
fi
;;
esac
if [ -z "$domains" ]; then
domains="$domain"
if [ -z "$items" ]; then
items="$item"
else
domains="$domains,$domain"
items="$items,$item"
fi
done < "$filepath"
if [ -z "$domains" ]; then
log "No valid domains found in $filepath"
if [ -z "$items" ]; then
log "No valid $type found in $filepath"
return 0
fi
domains="$(comma_string_to_json_array "$domains")"
sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "domain_suffix" "$domains"
items="$(comma_string_to_json_array "$items")"
case "$type" in
domains) sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "domain_suffix" "$items" ;;
subnets) sing_box_cm_patch_local_source_ruleset_rules "$ruleset_filepath" "ip_cidr" "$items" ;;
esac
}
configure_remote_domain_or_subnet_list_handler() {

View File

@@ -12,11 +12,10 @@ is_ipv4_cidr() {
[[ "$ip" =~ $regex ]]
}
# Check if string is valid domain
is_domain() {
local str="$1"
#local regex="^(?=.{1,253}(?:\/|$))(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+(?:[a-zA-Z]{2,}|xn--[a-zA-Z0-9-]{1,59}[a-zA-Z0-9])(?:\/[^\s]*)?$"
echo "$str" | grep -Eq '^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9]))+$'
#[[ $str =~ $regex ]]
}
# Checks if the given string is a valid base64-encoded sequence