Compare commits

..

25 Commits

Author SHA1 Message Date
itdoginfo
84115e2f3b v0.4.7 2025-08-25 17:02:46 +03:00
itdoginfo
2dbdb9d2c1 Global check: WG Route 2025-08-24 16:13:41 +03:00
itdoginfo
88c6717152 Disable delay 2025-08-24 13:44:49 +03:00
itdoginfo
b3986308ce Cut WRP prefix 2025-08-24 13:44:39 +03:00
itdoginfo
a15c3cf171 Update #147 2025-08-24 11:59:40 +03:00
itdoginfo
4c91223f85 Merge pull request #136 from SaltyMonkey:main
User Subnet validation for glob ip, init.d/zapret proper check, passwall in conflicts
2025-08-24 11:05:55 +03:00
itdoginfo
7cf7b1f626 Merge branch 'main' into main 2025-08-24 11:04:39 +03:00
SaltyMonkey
a2536534f8 Remove opkg list-installed checks for packages from conflicts 2025-08-24 10:00:13 +03:00
itdoginfo
c49354fe38 Merge pull request #149 from ampetelin:json_srs_lists
Added support for JSON and SRS
2025-08-23 18:53:37 +03:00
Andrey Petelin
6e01e036eb handle missing ip_cidr in rulesets 2025-08-23 20:42:43 +05:00
Andrey Petelin
7484d0c203 Fixed logging of custom ruleset preparation 2025-08-23 19:36:34 +05:00
Andrey Petelin
0eb4ca4ea9 Removed filepath from wget when downloading via proxy 2025-08-23 19:33:47 +05:00
Andrey Petelin
c2d95162b7 Added support for JSON and SRS rulesets 2025-08-20 21:42:46 +05:00
SaltyMonkey
1fc2947fbc Log messages for nextdns 2025-07-25 09:41:58 +03:00
SaltyMonkey
ea931d8463 added nextdns package in conflicts 2025-07-25 09:38:48 +03:00
SaltyMonkey
e2f36c35d4 Log messages for luci-app-passwall and luci-app-passwall in binary 2025-07-12 01:08:39 +03:00
SaltyMonkey
e8f8dcc5e7 added passwall and passwall2(paid version?) in conflicts 2025-07-12 01:00:00 +03:00
SaltyMonkey
1e2174bb80 User Subnets validation: 0.0.0.0 is not allowed 2025-07-12 00:40:51 +03:00
SaltyMonkey
85e515ef15 Fix /etc/init.d/zapret check in global_check, cleanup useless whitespaces 2025-07-12 00:29:33 +03:00
itdoginfo
418cdc4366 rm iptables-mod-extra check 2025-06-30 16:56:39 +03:00
itdoginfo
25b0dcaad5 v0.4.6 2025-06-30 16:27:44 +03:00
itdoginfo
cc59e756dd br_netfilter. Cache size unset. Mixed & source_ip_cidr 2025-06-30 16:26:31 +03:00
itdoginfo
210714c499 unnecessary check 2025-06-30 16:24:33 +03:00
itdoginfo
8b6c336584 Change to 1.1.1.1 2025-06-27 23:54:52 +03:00
itdoginfo
5c543c1608 Change to procd_add_interface_trigger. Added PROCD_RELOAD_DELAY 2025-06-27 23:54:31 +03:00
12 changed files with 630 additions and 296 deletions

1
.shellcheckrc Normal file
View File

@@ -0,0 +1 @@
disable=SC3036,SC3010,SC3014,SC3015,SC3020,SC3003

View File

@@ -2,7 +2,7 @@
- Это бета-версия, которая находится в активной разработке. Из версии в версию что-то может меняться.
- При возникновении проблем, нужен технически грамотный фидбэк в чат.
- При обновлении **обязательно** [сбрасывайте кэш LuCI](https://podkop.net/docs/clearbrowsercache/).
- При обновлении **обязательно** [сбрасывайте кэш LuCI](https://podkop.net/docs/clear-browser-cache/).
- Также при обновлении всегда заходите в конфигурацию и проверяйте свои настройки. Конфигурация может измениться.
- Необходимо минимум 15МБ свободного места на роутере. Роутеры с флешками на 16МБ сразу мимо.
- При старте программы редактируется конфиг Dnsmasq.
@@ -32,18 +32,13 @@ sh <(wget -O - https://raw.githubusercontent.com/itdoginfo/podkop/refs/heads/mai
- [ ] Возможно поменять структуру
## Списки
- [ ] Speedtest
- [x] Google AI
- [x] Google PlayMarket. Здесь уточнить, что точно не работает через корректную настройку FakeIP, а не dnsmasq+nft.
- [x] Hetzner ASN (AS24940)
- [x] OVH ASN (AS16276)
- [ ] CloudFront
## Будущее
- [ ] После наполнения вики про туннели, убрать всё что связано с их установкой из скрипта. Только с AWG что-то решить, лучше чтоб был скрипт в сторонем репозитории.
- [ ] Подписка. Здесь нужна реализация, чтоб для каждой секции помимо ручного выбора, был выбор фильтрации по тегу. Например, для main выбираем ключевые слова NL, DE, FI. А для extra секции фильтруем по RU. И создаётся outbound c urltest в которых перечислены outbound из фильтров.
- [ ] Опция, когда все запросы (с роутера в первую очередь), а не только br-lan идут в прокси. С этим связана #95. Требуется много переделать для nftables.
- [ ] Весь трафик в Proxy\VPN. Вопрос, что делать с экстрасекциями в этом случае. FakeIP здесь скорее не нужен, а значит только main секция остаётся. Всё что касается fakeip проверок, придётся выключать в этом режиме.
- [ ] Поддержка Source format. Нужна расшифровка в json и если присуствуют подсети, заносить их в custom subnet nftset.
- [x] Поддержка Source format. Нужна расшифровка в json и если присуствуют подсети, заносить их в custom subnet nftset.
- [ ] Переделывание функции формирования кастомных списков в JSON. Обрабатывать сразу скопом, а не по одному.
- [ ] При успешном запуске переходит в фоновый режим и следит за состоянием sing-box. Если вдруг идёт exit 1, выполняется dnsmasq restore и снова следит за состоянием. Вопрос в том, как это искусcтвенно провернуть. Попробовать положить прокси и посмотреть, останется ли работать DNS в этом случае. И здесь, вероятно, можно обойтись триггером в init.d.
- [ ] Галочка, которая режет доступ к doh серверам.

View File

@@ -57,6 +57,11 @@ vless://uuid@server:443?security=tls&sni=server&fp=chrome&type=ws&path=/websocke
vless://33333@example.com:443/?type=ws&encryption=none&path=%2Fwebsocket&security=tls&sni=example.com&fp=chrome#vless-tls-ws-4
```
7.
```
vless://id@sub.domain.example:443?type=ws&path=%2Fdir%2Fpath&host=sub.domain.example&security=tls#configname
```
## No security
```
vless://8b60389a-7a01-4365-9244-c87f12bb98cf@example.com:443?type=tcp&security=none#vless-tls-no-encrypt

View File

@@ -142,10 +142,6 @@ check_system() {
esac
done
fi
if opkg list-installed | grep -q "iptables-mod-extra"; then
msg "Found incompatible iptables packages. If you're using FriendlyWrt: https://t.me/itdogchat/44512/181082"
fi
}
sing_box() {

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-podkop
PKG_VERSION:=0.4.5
PKG_VERSION:=0.4.7
PKG_RELEASE:=1
LUCI_TITLE:=LuCI podkop app

View File

@@ -179,10 +179,6 @@ function createConfigSection(section, map, network) {
if (!params.get('pbk')) return _('Invalid VLESS URL: missing pbk parameter for reality security');
if (!params.get('fp')) return _('Invalid VLESS URL: missing fp parameter for reality security');
}
if (security === 'tls' && type !== 'tcp' && !params.get('sni')) {
return _('Invalid VLESS URL: missing sni parameter for tls security');
}
}
return true;
@@ -421,6 +417,9 @@ function createConfigSection(section, map, network) {
const subnetRegex = /^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/;
if (!subnetRegex.test(value)) return _('Invalid format. Use format: X.X.X.X or X.X.X.X/Y');
const [ip, cidr] = value.split('/');
if (ip === "0.0.0.0") {
return _('IP address 0.0.0.0 is not allowed');
}
const ipParts = ip.split('.');
for (const part of ipParts) {
const num = parseInt(part);

View File

@@ -232,6 +232,9 @@ msgstr "Неверный формат URL. URL должен начинаться
msgid "Invalid format. Use format: X.X.X.X or X.X.X.X/Y"
msgstr "Неверный формат. Используйте формат: X.X.X.X или X.X.X.X/Y"
msgid "IP address 0.0.0.0 is not allowed"
msgstr "IP адрес не может быть 0.0.0.0"
msgid "IP address parts must be between 0 and 255"
msgstr "Части IP-адреса должны быть между 0 и 255"

View File

@@ -232,6 +232,9 @@ msgstr ""
msgid "Invalid format. Use format: X.X.X.X or X.X.X.X/Y"
msgstr ""
msgid "IP address 0.0.0.0 is not allowed"
msgstr ""
msgid "IP address parts must be between 0 and 255"
msgstr ""

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=podkop
PKG_VERSION:=0.4.5
PKG_VERSION:=0.4.7
PKG_RELEASE:=1
PKG_MAINTAINER:=ITDog <podkop@itdog.info>
@@ -13,7 +13,7 @@ define Package/podkop
SECTION:=net
CATEGORY:=Network
DEPENDS:=+sing-box +curl +jq +kmod-nft-tproxy +coreutils-base64
CONFLICTS:=https-dns-proxy
CONFLICTS:=https-dns-proxy nextdns luci-app-passwall luci-app-passwall2
TITLE:=Domain routing app
URL:=https://podkop.net
PKGARCH:=all

View File

@@ -35,7 +35,7 @@ config main 'main'
option dns_server '8.8.8.8'
option split_dns_enabled '1'
option split_dns_type 'udp'
option split_dns_server '8.8.8.8'
option split_dns_server '1.1.1.1'
option dns_rewrite_ttl '60'
option cache_file '/tmp/cache.db'
list iface 'br-lan'

View File

@@ -35,12 +35,15 @@ service_triggers() {
config_get mon_restart_ifaces "main" "mon_restart_ifaces"
config_get restart_ifaces "main" "restart_ifaces"
# Test without delay
#PROCD_RELOAD_DELAY=2000
procd_open_trigger
procd_add_config_trigger "config.change" "$NAME" "$initscript" restart 'on_config_change'
if [ "$mon_restart_ifaces" = "1" ]; then
for iface in $restart_ifaces; do
procd_add_reload_interface_trigger $iface
procd_add_interface_trigger "interface.*.up" "$iface" /etc/init.d/podkop reload
done
fi
procd_close_trigger

View File

@@ -1,4 +1,5 @@
#!/bin/ash
# shellcheck shell=dash
[ -r /lib/functions.sh ] && . /lib/functions.sh
[ -r /lib/config/uci.sh ] && . /lib/config/uci.sh
@@ -26,7 +27,9 @@ TEST_DOMAIN="fakeip.podkop.fyi"
INTERFACES_LIST=""
SRC_INTERFACE=""
RESOLV_CONF="/etc/resolv.conf"
CLOUDFLARE_OCTETS="103.21 103.22 103.31 104.16 104.17 104.18 104.19 104.20 104.21 104.22 104.23 104.24 104.25 104.26 104.27 104.28 108.162 131.0 141.101 162.158 162.159 172.64 172.65 172.66 172.67 172.68 172.69 172.70 172.71 173.245 188.114 190.93 197.234 198.41"
# Endpoints https://github.com/ampetelin/warp-endpoint-checker
CLOUDFLARE_OCTETS="8.47 162.159 188.114"
# Color constants
COLOR_CYAN="\033[0;36m"
@@ -69,10 +72,6 @@ start_main() {
exit 1
fi
if opkg list-installed | grep -q iptables-mod-extra; then
log "[critical] Conflicting package detected: iptables-mod-extra"
fi
if grep -qE 'doh_backup_noresolv|doh_backup_server|doh_server' /etc/config/dhcp; then
log "[critical] Detected https-dns-proxy in dhcp config. Edit /etc/config/dhcp"
fi
@@ -81,6 +80,8 @@ start_main() {
config_foreach process_validate_service
br_netfilter_disable
# Sync time for DoH/DoT
/usr/sbin/ntpd -q -p 194.190.168.1 -p 216.239.35.0 -p 216.239.35.4 -p 162.159.200.1 -p 162.159.200.123
@@ -109,7 +110,9 @@ start_main() {
config_foreach sing_box_rule_preset
config_foreach process_domains_list_local
config_foreach process_subnet_for_section
config_foreach process_remote_ruleset_srs
config_foreach configure_community_lists
config_foreach configure_remote_domain_lists
config_foreach configure_remote_subnet_lists
config_foreach process_all_traffic_for_section
config_foreach add_cron_job
@@ -302,6 +305,14 @@ process_validate_service() {
fi
}
br_netfilter_disable() {
if lsmod | grep -q br_netfilter && [ "$(sysctl -n net.bridge.bridge-nf-call-iptables 2>/dev/null)" = "1" ]; then
log "br_netfilter enabled detected. Disabling"
sysctl -w net.bridge.bridge-nf-call-iptables=0
sysctl -w net.bridge.bridge-nf-call-ip6tables=0
fi
}
# Main funcs
route_table_rule_mark() {
@@ -419,8 +430,9 @@ dnsmasq_restore() {
log "Removing configuration for dnsmasq"
local cachesize=$(uci get dhcp.@dnsmasq[0].podkop_cachesize 2>/dev/null)
if [ -z "$cachesize" ]; then
if [[ "$cachesize" == "unset" ]]; then
log "dnsmasq revert: cachesize is unset"
uci -q delete dhcp.@dnsmasq[0].cachesize
else
uci set dhcp.@dnsmasq[0].cachesize="$cachesize"
fi
@@ -565,7 +577,7 @@ prepare_custom_ruleset() {
sing_box_rules $tag $section
sing_box_dns_rule_fakeip_section $tag $tag
log "Added 'test' rule_set to sing-box config"
log "Added $tag rule_set to sing-box config"
fi
}
@@ -613,9 +625,9 @@ list_update() {
echolog "📥 Downloading and processing lists..."
config_foreach process_remote_ruleset_subnet
config_foreach process_domains_list_url
config_foreach process_subnet_for_section_remote
config_foreach import_community_subnet_lists
config_foreach import_domains_from_remote_domain_lists
config_foreach import_subnets_from_remote_subnet_lists
if [ $? -eq 0 ]; then
echolog "✅ Lists update completed successfully"
@@ -957,7 +969,7 @@ sing_box_dns_rule_fakeip() {
sing_box_dns_rule_fakeip_section() {
local rule_set=$1
echo $rule_set
log "Adding section to fakeip route rules in sing-box"
jq \
@@ -1458,13 +1470,109 @@ sing_box_ruleset_subnets_json() {
log "$subnet added to $section-custom-domains-subnets.json"
}
#######################################
# Adds a new remote ruleset to the sing-box configuration.
# https://sing-box.sagernet.org/configuration/rule-set/#__tabbed_1_3
#
# Arguments:
# tag: unique identifier for the ruleset.
# format: format of the ruleset (e.g., "source" or "binary").
# url: URL from which the ruleset can be fetched.
# update_interval: update interval for the ruleset (e.g., "1d").
# detour: flag indicating whether to use a download detour ("1" or "0").
#
# Outputs:
# Modifies the sing-box configuration file by appending a new ruleset entry.
#
# Returns:
# None. Always returns 0. If a ruleset with the same tag exists, it is skipped.
#######################################
sing_box_config_add_remote_ruleset() {
local tag=$1
local format=$2
local url=$3
local update_interval=$4
local detour=$5
local tag_exists
tag_exists=$(jq -r --arg tag "$tag" '
.route.rule_set[]? | select(.tag == $tag) | .tag
' "$SING_BOX_CONFIG")
if [[ -n "$tag_exists" ]]; then
log "Ruleset with tag $tag already exists. Skipping addition."
else
jq \
--arg tag "$tag" \
--arg format "$format" \
--arg url "$url" \
--arg update_interval "$update_interval" \
--arg detour "$detour" \
'
.route.rule_set += [
(
{
"tag": $tag,
"type": "remote",
"format": $format,
"url": $url,
"update_interval": $update_interval
} +
(if $detour == "1" then {"download_detour": "main"} else {} end)
)
]' "$SING_BOX_CONFIG" | build_sing_box_config
log "Added new remote ruleset with tag $tag"
fi
}
#######################################
# Adds a remote ruleset to the sing-box configuration and applies route and dns rules.
#
# Arguments:
# url: remote ruleset URL.
# section: configuration section where rules will be applied.
# ruleset_content_type: Type of ruleset content (e.g., "domains" or "subnets").
#
# Returns:
# 0 on success, non-zero if the file extension is unsupported.
#######################################
sing_box_add_remote_ruleset_and_rules() {
local url="$1"
local section="$2"
local ruleset_content_type="$3"
local tag
local format
local update_interval='1d'
local detour
case "$(get_url_file_extension "$url")" in
json) format="source" ;;
srs) format="binary" ;;
*)
log "Unsupported file extension: .$file_extension"
return 1
;;
esac
tag=$(get_ruleset_tag_from_url "$url" "$section-remote-$ruleset_content_type")
config_get_bool detour "main" "detour" "0"
sing_box_config_add_remote_ruleset "$tag" "$format" "$url" "$update_interval" "$detour"
sing_box_rules "$tag" "$section"
if [[ "$ruleset_content_type" = "domains" ]]; then
sing_box_dns_rule_fakeip_section "$tag"
fi
}
process_domains_for_section() {
local section="$1"
config_get custom_domains_list_type "$section" "custom_domains_list_type" "disabled"
if [ "$custom_domains_list_type" != "disabled" ]; then
log "Adding a custom domains list for section $section"
log "Adding a custom domains list for $section section"
if [ "$custom_domains_list_type" = "dynamic" ]; then
# Handle list domains from custom_domains
config_list_foreach "$section" custom_domains "sing_box_ruleset_domains" "$section"
@@ -1476,96 +1584,6 @@ process_domains_for_section() {
fi
}
sing_box_ruleset_remote() {
local tag=$1
local type=$2
local update_interval=$3
local detour=$4
url="$SRS_MAIN_URL/$tag.srs"
local tag_exists=$(jq -r --arg tag "$tag" '
.route.rule_set[]? | select(.tag == $tag) | .tag
' "$SING_BOX_CONFIG")
if [[ -n "$tag_exists" ]]; then
log "Ruleset with tag $tag already exists. Skipping addition."
else
jq \
--arg tag "$tag" \
--arg type "$type" \
--arg url "$url" \
--arg update_interval "$update_interval" \
--arg detour "$detour" \
'
.route.rule_set += [
(
{
"tag": $tag,
"type": $type,
"format": "binary",
"url": $url,
"update_interval": $update_interval
} +
(if $detour == "1" then {"download_detour": "main"} else {} end)
)
]' "$SING_BOX_CONFIG" | build_sing_box_config
log "Added new ruleset with tag $tag"
fi
}
list_subnets_download() {
local service="$1"
local table="PodkopTable"
case "$service" in
"twitter")
URL=$SUBNETS_TWITTER
;;
"meta")
URL=$SUBNETS_META
;;
"telegram")
URL=$SUBNETS_TELERAM
;;
"cloudflare")
URL=$SUBNETS_CLOUDFLARE
;;
"hetzner")
URL=$SUBNETS_HETZNER
;;
"ovh")
URL=$SUBNETS_OVH
;;
"discord")
URL=$SUBNETS_DISCORD
nft add set inet $table podkop_discord_subnets { type ipv4_addr\; flags interval\; auto-merge\; }
nft add rule inet $table mangle iifname "$SRC_INTERFACE" ip daddr @podkop_discord_subnets udp dport { 50000-65535 } meta mark set 0x105 counter
;;
*)
return
;;
esac
local filename=$(basename "$URL")
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "/tmp/podkop/$filename" "$URL"
else
wget -O "/tmp/podkop/$filename" "$URL"
fi
while IFS= read -r subnet; do
if [ "$service" = "discord" ]; then
nft add element inet $table podkop_discord_subnets { $subnet }
else
nft add element inet $table podkop_subnets { $subnet }
fi
done <"/tmp/podkop/$filename"
}
sing_box_rules() {
log "Configure rule in sing-box"
local rule_set="$1"
@@ -1641,29 +1659,11 @@ sing_box_quic_reject() {
fi
}
process_remote_ruleset_srs() {
config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
if [ "$domain_list_enabled" -eq 1 ]; then
config_get_bool detour "main" "detour" "0"
log "Adding a srs list for $section"
config_list_foreach "$section" domain_list "sing_box_ruleset_remote" "remote" "1d" "$detour"
fi
}
process_remote_ruleset_subnet() {
config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
if [ "$domain_list_enabled" -eq 1 ]; then
log "Adding a srs list for $section"
config_list_foreach "$section" domain_list "list_subnets_download" "$section" "$domain_list"
fi
}
# TODO(ampetelin): function needs refactoring
sing_box_rule_preset() {
config_get custom_domains_list_type "$section" "custom_domains_list_type"
config_get custom_subnets_list_enabled "$section" "custom_subnets_list_enabled"
config_get custom_local_domains_list_enabled "$section" "custom_local_domains_list_enabled"
# config_get custom_download_domains_list_enabled "$section" "custom_download_domains_list_enabled"
# config_get custom_download_subnets_list_enabled "$section" "custom_download_subnets_list_enabled"
if [ "$custom_domains_list_type" != "disabled" ] || [ "$custom_subnets_list_enabled" != "disabled" ] ||
[ "$custom_local_domains_list_enabled" = "1" ]; then
@@ -1708,46 +1708,12 @@ process_domains_list_local() {
fi
}
list_custom_url_domains_create() {
local section="$2"
local URL="$1"
local filename=$(basename "$URL")
local filepath="/tmp/podkop/${filename}"
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "$filepath" "$URL"
else
wget -O "$filepath" "$URL"
fi
if grep -q $'\r' "$filepath"; then
log "$filename has Windows line endings (CRLF). Converting to Unix (LF)"
sed -i 's/\r$//' "$filepath"
fi
while IFS= read -r domain; do
log "From downloaded file: $domain"
sing_box_ruleset_domains_json $domain $section
done <"$filepath"
}
process_domains_list_url() {
local section="$1"
config_get custom_download_domains_list_enabled "$section" "custom_download_domains_list_enabled"
if [ "$custom_download_domains_list_enabled" -eq 1 ]; then
log "Adding a custom domains list from URL in $section"
config_list_foreach "$section" "custom_download_domains" list_custom_url_domains_create "$section"
fi
}
process_subnet_for_section() {
local section="$1"
config_get custom_subnets_list_enabled "$section" "custom_subnets_list_enabled" "disabled"
if [ "$custom_subnets_list_enabled" != "disabled" ]; then
log "Adding a custom subnet list for section $section"
log "Adding a custom subnet list for $section section"
if [ "$custom_subnets_list_enabled" = "dynamic" ]; then
# Handle list domains from custom_domains
config_list_foreach "$section" custom_subnets "sing_box_ruleset_subnets" "$section"
@@ -1759,66 +1725,327 @@ process_subnet_for_section() {
fi
}
list_custom_url_subnets_create() {
local section="$2"
local URL="$1"
local filename=$(basename "$URL")
local filepath="/tmp/podkop/${filename}"
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "$filepath" "$URL"
else
wget -O "$filepath" "$URL"
configure_community_lists() {
config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
if [ "$domain_list_enabled" -eq 1 ]; then
log "Configuring community lists for $section section"
config_list_foreach "$section" domain_list configure_community_list_handler
fi
if grep -q $'\r' "$filepath"; then
log "$filename has Windows line endings (CRLF). Converting to Unix (LF)"
sed -i 's/\r$//' "$filepath"
fi
while IFS= read -r subnet; do
log "From local file: $subnet"
sing_box_ruleset_subnets_json $subnet $section
nft add element inet PodkopTable podkop_subnets { $subnet }
done <"$filepath"
}
process_subnet_for_section_remote() {
configure_community_list_handler() {
local tag=$1
local format="binary"
local update_interval="1d"
config_get_bool detour "main" "detour" "0"
local url="$SRS_MAIN_URL/$tag.srs"
sing_box_config_add_remote_ruleset "$tag" "$format" "$url" "$update_interval" "$detour"
}
configure_remote_domain_lists() {
local section="$1"
config_get custom_download_subnets_list_enabled "$section" "custom_download_subnets_list_enabled" "disabled"
if [ "$custom_download_subnets_list_enabled" -eq "1" ]; then
log "Adding a custom SUBNET list from URL in $section"
config_list_foreach "$section" "custom_download_subnets" list_custom_url_subnets_create "$section"
config_get custom_download_domains_list_enabled "$section" custom_download_domains_list_enabled
if [ "$custom_download_domains_list_enabled" -eq 1 ]; then
log "Configuring remote domain lists for $section section"
config_list_foreach "$section" custom_download_domains configure_remote_domain_list_handler "$section"
fi
}
configure_remote_domain_list_handler() {
local url="$1"
local section="$2"
log "Configuring remote domain list from URL: $url"
local file_extension
file_extension=$(get_url_file_extension "$url")
case "$file_extension" in
lst)
log "Detected file extension: .$file_extension → no processing needed, managed on list_update"
;;
json|srs)
log "Detected file extension: .$file_extension → proceeding with processing"
sing_box_add_remote_ruleset_and_rules "$url" "$section" "domains"
;;
*)
log "Detected file extension: .$file_extension → unsupported, skipping"
return 1
;;
esac
}
configure_remote_subnet_lists() {
local section="$1"
config_get custom_download_subnets_list_enabled "$section" custom_download_subnets_list_enabled disabled
if [ "$custom_download_subnets_list_enabled" -eq "1" ]; then
log "Configuring remote subnet lists for $section section"
config_list_foreach "$section" custom_download_subnets configure_remote_subnet_list_handler "$section"
fi
}
configure_remote_subnet_list_handler() {
local url="$1"
local section="$2"
log "Configuring remote subnet list from URL: $url"
local file_extension
file_extension=$(get_url_file_extension "$url")
case "$file_extension" in
lst)
log "Detected file extension: .$file_extension → no processing needed, managed on list_update"
;;
json|srs)
log "Detected file extension: .$file_extension → proceeding with processing"
sing_box_add_remote_ruleset_and_rules "$url" "$section" "subnets"
;;
*)
log "Detected file extension: .$file_extension → unsupported, skipping"
return 1
;;
esac
}
process_all_traffic_for_section() {
local section="$1"
config_get all_traffic_from_ip_enabled "$section" "all_traffic_from_ip_enabled"
if [ "$all_traffic_from_ip_enabled" -eq "1" ]; then
log "Adding an IP to redirect all traffic"
config_list_foreach $section all_traffic_ip list_all_traffic_from_ip
config_list_foreach $section all_traffic_ip nft_list_all_traffic_from_ip
config_list_foreach $section all_traffic_ip sing_box_rules_source_ip_cidr $all_traffic_ip $section
fi
}
import_community_subnet_lists() {
config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
if [ "$domain_list_enabled" -eq 1 ]; then
log "Importing community subnet lists for $section section"
config_list_foreach "$section" domain_list import_community_service_subnet_list_handler
fi
}
import_community_service_subnet_list_handler() {
local service="$1"
local table="PodkopTable"
case "$service" in
"twitter")
URL=$SUBNETS_TWITTER
;;
"meta")
URL=$SUBNETS_META
;;
"telegram")
URL=$SUBNETS_TELERAM
;;
"cloudflare")
URL=$SUBNETS_CLOUDFLARE
;;
"hetzner")
URL=$SUBNETS_HETZNER
;;
"ovh")
URL=$SUBNETS_OVH
;;
"discord")
URL=$SUBNETS_DISCORD
nft add set inet $table podkop_discord_subnets { type ipv4_addr\; flags interval\; auto-merge\; }
nft add rule inet $table mangle iifname "$SRC_INTERFACE" ip daddr @podkop_discord_subnets udp dport { 50000-65535 } meta mark set 0x105 counter
;;
*)
return
;;
esac
local filename=$(basename "$URL")
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "/tmp/podkop/$filename" "$URL"
else
wget -O "/tmp/podkop/$filename" "$URL"
fi
while IFS= read -r subnet; do
if [ "$service" = "discord" ]; then
nft add element inet $table podkop_discord_subnets { $subnet }
else
nft add element inet $table podkop_subnets { $subnet }
fi
done <"/tmp/podkop/$filename"
}
import_domains_from_remote_domain_lists() {
local section="$1"
config_get custom_download_domains_list_enabled "$section" custom_download_domains_list_enabled
if [ "$custom_download_domains_list_enabled" -eq 1 ]; then
log "Importing domains from remote domain lists for $section section"
config_list_foreach "$section" custom_download_domains import_domains_from_remote_domain_list_handler "$section"
fi
}
import_domains_from_remote_domain_list_handler() {
local url="$1"
local section="$2"
log "Importing domains from URL: $url"
local file_extension
file_extension=$(get_url_file_extension "$url")
case "$file_extension" in
lst)
log "Detected file extension: .$file_extension → proceeding with processing"
import_domains_from_remote_lst_file "$url" "$section"
;;
json|srs)
log "Detected file extension: .$file_extension → no update needed, sing-box manages updates"
;;
*)
log "Detected file extension: .$file_extension → unsupported, skipping"
return 1
;;
esac
}
import_domains_from_remote_lst_file() {
local url="$1"
local section="$2"
local filename
filename=$(basename "$url")
local filepath="/tmp/podkop/${filename}"
download_to_tempfile "$url" "$filepath"
while IFS= read -r domain; do
sing_box_ruleset_domains_json $domain $section
done <"$filepath"
rm -f "$filepath"
}
import_subnets_from_remote_subnet_lists() {
local section="$1"
config_get custom_download_subnets_list_enabled "$section" custom_download_subnets_list_enabled disabled
if [ "$custom_download_subnets_list_enabled" -eq "1" ]; then
log "Importing subnets from remote subnet lists for $section section"
config_list_foreach "$section" custom_download_subnets import_subnets_from_remote_subnet_list_handler "$section"
fi
}
import_subnets_from_remote_subnet_list_handler() {
local url="$1"
local section="$2"
log "Importing subnets from URL: $url"
local file_extension
file_extension=$(get_url_file_extension "$url")
case "$file_extension" in
lst)
log "Detected file extension: .$file_extension → proceeding with processing"
import_subnets_from_remote_lst_file "$url" "$section"
;;
json)
log "Detected file extension: .$file_extension → proceeding with processing"
import_subnets_from_remote_json_file "$url"
;;
srs)
log "Detected file extension: .$file_extension → proceeding with processing"
import_subnets_from_remote_srs_file "$url"
;;
*)
log "Detected file extension: .$file_extension → unsupported, skipping"
return 1
;;
esac
}
import_subnets_from_remote_lst_file() {
local url="$1"
local section="$2"
local filename
filename=$(basename "$url")
local filepath="/tmp/podkop/${filename}"
download_to_tempfile "$url" "$filepath"
while IFS= read -r subnet; do
sing_box_ruleset_subnets_json "$subnet" "$section"
nft_add_podkop_subnet "$subnet"
done <"$filepath"
rm -f "$filepath"
}
import_subnets_from_remote_json_file() {
local url="$1"
download_to_stream "$url" | jq -r '.rules[].ip_cidr[]?' | while read -r subnet; do
nft_add_podkop_subnet "$subnet"
done
}
import_subnets_from_remote_srs_file() {
local url="$1"
local filename
filename=$(basename "$url")
local binary_filepath="/tmp/podkop/${filename}"
local json_filepath="/tmp/podkop/decompiled-${filename%%.*}.json"
download_to_tempfile "$url" "$binary_filepath"
if ! decompile_srs_file "$binary_filepath" "$json_filepath"; then
return 1
fi
jq -r '.rules[].ip_cidr[]' "$json_filepath" | while read -r subnet; do
nft_add_podkop_subnet "$subnet"
done
rm -f "$binary_filepath" "$json_filepath"
}
# Decompiles a sing-box SRS binary file into a JSON ruleset file
decompile_srs_file() {
local binary_filepath="$1"
local output_filepath="$2"
log "Decompiling $binary_filepath to $output_filepath"
if ! file_exists "$binary_filepath"; then
log "File $binary_filepath not found"
return 1
fi
sing-box rule-set decompile "$binary_filepath" -o "$output_filepath"
if [[ $? -ne 0 ]]; then
log "Decompilation command failed for $binary_filepath"
return 1
fi
}
sing_box_rules_source_ip_cidr() {
log "Configure source_ip_cidr rule in sing-box"
local source_ip_cidr="$1"
local outbound="$2"
local current_source_ip_cidr=$(jq -r '.route.rules[] | select(.outbound == "'"$outbound"'" and .action == "route" and (.rule_set | not))' $SING_BOX_CONFIG)
local current_source_ip_cidr=$(jq -r '.route.rules[] | select(.outbound == "'"$outbound"'" and .action == "route" and .source_ip_cidr and (.inbound // [] | contains(["tproxy-in"])))' $SING_BOX_CONFIG)
if [[ -n "$current_source_ip_cidr" ]]; then
jq \
--arg source_ip_cidr "$source_ip_cidr" \
--arg outbound "$outbound" \
'(.route.rules[] | select(.outbound == $outbound and .action == "route" and (.rule_set | not)) | .source_ip_cidr) += [$source_ip_cidr]' \
"$SING_BOX_CONFIG" | build_sing_box_config
'(.route.rules[] | select(.outbound == $outbound and .action == "route" and .source_ip_cidr and (.inbound // [] | contains(["tproxy-in"]))) | .source_ip_cidr) += [$source_ip_cidr]' "$SING_BOX_CONFIG" | build_sing_box_config
else
jq \
--arg source_ip_cidr "$source_ip_cidr" \
@@ -1860,7 +2087,7 @@ detour_mixed() {
}
## nftables
list_all_traffic_from_ip() {
nft_list_all_traffic_from_ip() {
local ip="$1"
local table="PodkopTable"
@@ -1885,6 +2112,17 @@ list_all_traffic_from_ip() {
fi
}
# Adds an IPv4 subnet to nftables firewall set
nft_add_podkop_subnet() {
local subnet="$1"
if is_ipv4_cidr "$subnet"; then
nft add element inet PodkopTable podkop_subnets { "$subnet" }
else
log "Invalid subnet format. $subnet is not a valid IPv4 CIDR"
fi
}
# Diagnotics
check_proxy() {
if ! command -v sing-box >/dev/null 2>&1; then
@@ -2542,7 +2780,17 @@ global_check() {
done
fi
if [ -d "/etc/init.d/zapret" ]; then
if uci show network | grep -q route_allowed_ips; then
uci show network | grep route_allowed_ips | cut -d"'" -f2 | while read -r value; do
if [ "$value" = "1" ]; then
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "⚠️ WG Route allowed IP enabled"
continue
fi
done
fi
if [ -f "/etc/init.d/zapret" ]; then
print_global "━━━━━━━━━━━━━━━━━━━━━━━━━━━"
print_global "⚠️ Zapret detected"
fi
@@ -2597,6 +2845,87 @@ global_check() {
fi
}
# Download URL content directly
download_to_stream() {
local url="$1"
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -qO- "$url" | sed 's/\r$//'
else
wget -qO- "$url" | sed 's/\r$//'
fi
}
# Download URL to temporary file
download_to_tempfile() {
local url="$1"
local filepath="$2"
config_get_bool detour "main" "detour" "0"
if [ "$detour" -eq 1 ]; then
http_proxy="http://127.0.0.1:4534" https_proxy="http://127.0.0.1:4534" wget -O "$filepath" "$url"
else
wget -O "$filepath" "$url"
fi
if grep -q $'\r' "$filepath"; then
log "$filename has Windows line endings (CRLF). Converting to Unix (LF)"
sed -i 's/\r$//' "$filepath"
fi
}
# helper function
# check if file exists
file_exists() {
local filepath="$1"
if [[ -f "$filepath" ]]; then
return 0 # success
else
return 1 # failure
fi
}
# extracts file extension from URL
get_url_file_extension() {
local url="$1"
local file_extension="${url##*.}"
echo "$file_extension"
}
# extracts file extension from URL
get_ruleset_tag_from_url() {
local url="$1"
local prefix="${2:-}"
local postfix="${3:-}"
local filename="${url##*/}"
local basename="${filename%%.*}"
local tag="$basename"
if [ -n "$prefix" ]; then
tag="${prefix}-${tag}"
fi
if [ -n "$postfix" ]; then
tag="${tag}-${postfix}"
fi
echo "$tag"
}
# check if string is valid IPv4 with CIDR mask
is_ipv4_cidr() {
local ip="$1"
local regex="^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}(\/(3[0-2]|2[0-9]|1[0-9]|[0-9]))$"
[[ $ip =~ $regex ]]
}
show_help() {
cat << EOF
Usage: $0 COMMAND