diff --git a/luci-app-podkop/Makefile b/luci-app-podkop/Makefile
index 0829d75..c74090b 100644
--- a/luci-app-podkop/Makefile
+++ b/luci-app-podkop/Makefile
@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-podkop
-PKG_VERSION:=0.3.22
+PKG_VERSION:=0.3.23
PKG_RELEASE:=1
LUCI_TITLE:=LuCI podkop app
diff --git a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js
index 5af0fb7..d5a56ae 100644
--- a/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js
+++ b/luci-app-podkop/htdocs/luci-static/resources/view/podkop/podkop.js
@@ -589,7 +589,7 @@ const createModalContent = (title, content) => {
};
const showConfigModal = async (command, title) => {
- const res = await safeExec('/etc/init.d/podkop', [command]);
+ const res = await safeExec('/usr/bin/podkop', [command]);
const formattedOutput = formatDiagnosticOutput(res.stdout || _('No output'));
ui.showModal(_(title), createModalContent(title, formattedOutput));
};
@@ -608,7 +608,7 @@ const ButtonFactory = {
return this.createButton({
label: config.label,
additionalClass: `cbi-button-${config.type || ''}`,
- onClick: () => safeExec('/etc/init.d/podkop', [config.action])
+ onClick: () => safeExec('/usr/bin/podkop', [config.action])
.then(() => config.reload && location.reload()),
style: config.style
});
@@ -787,7 +787,7 @@ return view.extend({
// Additional Settings Tab (main section)
let o = mainSection.tab('additional', _('Additional Settings'));
- o = mainSection.taboption('additional', form.Flag, 'yacd', _('Yacd enable'), _('http://openwrt.lan:9090/ui'));
+ o = mainSection.taboption('additional', form.Flag, 'yacd', _('Yacd enable'), _('openwrt.lan:9090/ui'));
o.default = '0';
o.rmempty = false;
o.ucisection = 'main';
@@ -943,7 +943,7 @@ return view.extend({
return new Promise(async (resolve) => {
try {
- const singboxStatusResult = await safeExec('/etc/init.d/podkop', ['get_sing_box_status']);
+ const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
if (!singboxStatus.running) {
@@ -991,12 +991,12 @@ return view.extend({
system,
fakeipStatus
] = await Promise.all([
- safeExec('/etc/init.d/podkop', ['get_status']),
- safeExec('/etc/init.d/podkop', ['get_sing_box_status']),
- safeExec('/etc/init.d/podkop', ['show_version']),
- safeExec('/etc/init.d/podkop', ['show_luci_version']),
- safeExec('/etc/init.d/podkop', ['show_sing_box_version']),
- safeExec('/etc/init.d/podkop', ['show_system_info']),
+ safeExec('/usr/bin/podkop', ['get_status']),
+ safeExec('/usr/bin/podkop', ['get_sing_box_status']),
+ safeExec('/usr/bin/podkop', ['show_version']),
+ safeExec('/usr/bin/podkop', ['show_luci_version']),
+ safeExec('/usr/bin/podkop', ['show_sing_box_version']),
+ safeExec('/usr/bin/podkop', ['show_system_info']),
checkFakeIP()
]);
@@ -1039,14 +1039,14 @@ return view.extend({
const updateStatus = async () => {
try {
if (!versionReceived) {
- const version = await safeExec('/etc/init.d/podkop', ['show_version'], 2000);
+ const version = await safeExec('/usr/bin/podkop', ['show_version'], 2000);
if (version.stdout) {
versionText = _('Podkop') + ' v' + version.stdout.trim();
versionReceived = true;
}
}
- const singboxStatusResult = await safeExec('/etc/init.d/podkop', ['get_sing_box_status']);
+ const singboxStatusResult = await safeExec('/usr/bin/podkop', ['get_sing_box_status']);
const singboxStatus = JSON.parse(singboxStatusResult.stdout || '{"running":0,"dns_configured":0}');
const fakeipStatus = await checkFakeIP();
diff --git a/luci-app-podkop/root/usr/share/rpcd/acl.d/luci-app-podkop.json b/luci-app-podkop/root/usr/share/rpcd/acl.d/luci-app-podkop.json
index 10ede7c..6d0eabc 100644
--- a/luci-app-podkop/root/usr/share/rpcd/acl.d/luci-app-podkop.json
+++ b/luci-app-podkop/root/usr/share/rpcd/acl.d/luci-app-podkop.json
@@ -5,6 +5,9 @@
"file": {
"/etc/init.d/podkop": [
"exec"
+ ],
+ "/usr/bin/podkop": [
+ "exec"
]
},
"ubus": {
diff --git a/podkop/Makefile b/podkop/Makefile
index 93836b1..c6d82d6 100644
--- a/podkop/Makefile
+++ b/podkop/Makefile
@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=podkop
-PKG_VERSION:=0.3.22
+PKG_VERSION:=0.3.23
PKG_RELEASE:=1
PKG_MAINTAINER:=ITDog
@@ -49,6 +49,9 @@ define Package/podkop/install
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/etc/config/podkop $(1)/etc/config/podkop
+
+ $(INSTALL_DIR) $(1)/usr/bin
+ $(INSTALL_BIN) ./files/usr/bin/podkop $(1)/usr/bin/podkop
endef
$(eval $(call BuildPackage,podkop))
diff --git a/podkop/files/etc/init.d/podkop b/podkop/files/etc/init.d/podkop
index 7d0f10b..7f0d8e2 100755
--- a/podkop/files/etc/init.d/podkop
+++ b/podkop/files/etc/init.d/podkop
@@ -7,41 +7,8 @@ script=$(readlink "$initscript")
NAME="$(basename ${script:-$initscript})"
config_load "$NAME"
-EXTRA_COMMANDS="main list_update check_proxy check_nft check_github check_logs check_sing_box_connections check_sing_box_logs check_dnsmasq show_config show_version show_sing_box_config show_luci_version show_sing_box_version show_system_info get_status get_sing_box_status"
-EXTRA_HELP=" list_update Updating domain and subnet lists
- check_proxy Check if sing-box proxy works correctly
- check_nft Show PodkopTable nftables rules
- check_github Check GitHub connectivity and lists availability
- check_logs Show podkop logs from system journal
- check_sing_box_connections Show active sing-box network connections
- check_sing_box_logs Show recent sing-box logs
- check_dnsmasq Show current DNSMasq configuration
- show_config Show current configuration with masked sensitive data
- show_version Show current version
- show_sing_box_config Show current sing-box configuration
- show_luci_version Show LuCI app version
- show_sing_box_version Show sing-box version
- show_system_info Show OpenWrt version and device model
- get_sing_box_status Get sing-box status"
-
-GITHUB_RAW_URL="https://raw.githubusercontent.com/itdoginfo/allow-domains/main"
-SRS_MAIN_URL="https://github.com/itdoginfo/allow-domains/releases/latest/download"
-DOMAINS_RU_INSIDE="${GITHUB_RAW_URL}/Russia/inside-dnsmasq-nfset.lst"
-DOMAINS_RU_OUTSIDE="${GITHUB_RAW_URL}/Russia/outside-dnsmasq-nfset.lst"
-DOMAINS_UA="${GITHUB_RAW_URL}/Ukraine/inside-dnsmasq-nfset.lst"
-DOMAINS_YOUTUBE="${GITHUB_RAW_URL}/Services/youtube.lst"
-SUBNETS_TWITTER="${GITHUB_RAW_URL}/Subnets/IPv4/twitter.lst"
-SUBNETS_META="${GITHUB_RAW_URL}/Subnets/IPv4/meta.lst"
-SUBNETS_DISCORD="${GITHUB_RAW_URL}/Subnets/IPv4/discord.lst"
-SUBNETS_TELERAM="${GITHUB_RAW_URL}/Subnets/IPv4/telegram.lst"
-SING_BOX_CONFIG="/etc/sing-box/config.json"
-FAKEIP="198.18.0.0/15"
-VALID_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube discord meta twitter hdrezka tiktok telegram"
-DNS_RESOLVERS="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 9.9.9.11 94.140.14.14 94.140.15.15 208.67.220.220 208.67.222.222 77.88.8.1 77.88.8.8"
-TEST_DOMAIN="google.com"
-
start_service() {
- log "Start podkop"
+ echo "Start podkop"
sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}')
required_version="1.11.1"
@@ -60,51 +27,19 @@ start_service() {
fi
if ! ip addr | grep -q "br-lan"; then
- log "Interface br-lan not found"
+ echo "Interface br-lan not found"
exit 1
fi
- migration
-
- config_foreach process_validate_service
-
procd_open_instance
- procd_set_param command /bin/sh -c "/etc/init.d/podkop main &"
+ procd_set_param command /bin/sh -c "/usr/bin/podkop start"
procd_set_param stdout 1
procd_set_param stderr 1
procd_close_instance
}
stop_service() {
- log "Stopping the podkop"
- remove_cron_job
-
- config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0"
- if [ "$dont_touch_dhcp" -eq 0 ]; then
- dnsmasq_restore
- fi
-
- rm -rf /tmp/podkop/*.lst
-
- log "Flush nft"
- if nft list table inet PodkopTable >/dev/null 2>&1; then
- nft delete table inet PodkopTable
- fi
-
- log "Flush ip rule"
- if ip rule list | grep -q "podkop"; then
- ip rule del fwmark 0x105 table podkop priority 105
- fi
-
- log "Flush ip route"
- if ip route list table podkop >/dev/null 2>&1; then
- ip route flush table podkop
- fi
-
- log "Stop sing-box"
- /etc/init.d/sing-box stop
- /etc/init.d/sing-box disable
-
+ /usr/bin/podkop stop
}
restart_service() {
@@ -118,1728 +53,6 @@ reload_service() {
}
service_triggers() {
- log "service_triggers start"
+ echo "service_triggers start"
procd_add_config_trigger "config.change" "$NAME" "$initscript" reload 'on_config_change'
-}
-
-log() {
- local message="$1"
- local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
- local CYAN="\033[0;36m"
- local GREEN="\033[0;32m"
- local RESET="\033[0m"
-
- echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}"
- logger -t "podkop" "$timestamp $message"
-}
-
-nolog() {
- local message="$1"
- local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
- local CYAN="\033[0;36m"
- local GREEN="\033[0;32m"
- local RESET="\033[0m"
-
- echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}"
-}
-
-main() {
- sleep 3
-
- mkdir -p /tmp/podkop
-
- # base
- route_table_rule_mark
- create_nft_table
- sing_box_uci
-
- # sing-box
- sing_box_inbound_proxy 1602
- sing_box_dns
- sing_box_dns_rule_fakeip
- sing_box_rule_dns
- sing_box_add_secure_dns_probe_domain
- sing_box_cache_file
- process_socks5
-
- # sing-box outbounds and rules
- config_foreach sing_box_outdound
- config_foreach process_domains_for_section
- config_foreach process_remote_ruleset
- config_foreach sing_box_rule_preset
- config_foreach process_domains_list_local
- config_foreach process_domains_list_url
- config_foreach process_subnet_for_section
- config_foreach process_subnet_for_section_remote
- config_foreach process_all_traffic_for_section
- config_foreach add_cron_job
-
- # Future: exclude at the fakeip?
- config_get_bool exclude_from_ip_enabled "main" "exclude_from_ip_enabled" "0"
- if [ "$exclude_from_ip_enabled" -eq 1 ]; then
- log "Adding an IP for exclusion"
- config_list_foreach main exclude_traffic_ip sing_box_rules_source_ip_cidr $exclude_traffic_ip direct-out
- fi
-
- config_get_bool yacd "main" "yacd" "0"
- if [ "$yacd" -eq 1 ]; then
- log "Yacd enable"
- jq '.experimental.clash_api = {
- "external_ui": "ui",
- "external_controller": "0.0.0.0:9090"
- }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
- fi
-
- config_get_bool exclude_ntp "main" "exclude_ntp" "0"
- if [ "$exclude_ntp" -eq 1 ]; then
- log "NTP traffic exclude for proxy"
- nft insert rule inet PodkopTable mangle udp dport 123 return
- fi
-
- config_get_bool quic_disable "main" "quic_disable" "0"
- if [ "$quic_disable" -eq 1 ]; then
- log "Rule for disable QUIC"
- sing_box_quic_reject
- fi
-
- sing_box_config_check
- /etc/init.d/sing-box restart
- /etc/init.d/sing-box enable
-
- config_get proxy_string "main" "proxy_string"
- config_get interface "main" "interface"
- config_get outbound_json "main" "outbound_json"
-
- if [ -n "$proxy_string" ] || [ -n "$interface" ] || [ -n "$outbound_json" ]; then
- config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0"
- if [ "$dont_touch_dhcp" -eq 0 ]; then
- dnsmasq_add_resolver
- fi
- fi
-}
-
-# Migrations and validation funcs
-migration() {
- # list migrate
- local CONFIG="/etc/config/podkop"
-
- if grep -q "ru_inside" $CONFIG; then
- log "Depricated list found: ru_inside"
- sed -i '/ru_inside/d' $CONFIG
- fi
-
- if grep -q "list domain_list 'ru_outside'" $CONFIG; then
- log "Depricated list found: sru_outside"
- sed -i '/ru_outside/d' $CONFIG
- fi
-
- if grep -q "list domain_list 'ua'" $CONFIG; then
- log "Depricated list found: ua"
- sed -i '/ua/d' $CONFIG
- fi
-
- # Subnet list
- if grep -q "list subnets" $CONFIG; then
- log "Depricated second section found"
- sed -i '/list subnets/d' $CONFIG
- fi
-
- # second remove
- if grep -q "config second 'second'" $CONFIG; then
- log "Depricated second section found"
- sed -i '/second/d' $CONFIG
- fi
-
- # cron update
- if grep -qE "^\s*option update_interval '[0-9*/,-]+( [0-9*/,-]+){4}'" $CONFIG; then
- log "Depricated update_interval"
- sed -i "s|^\(\s*option update_interval\) '[0-9*/,-]\+\( [0-9*/,-]\+\)\{4\}'|\1 '1d'|" $CONFIG
- fi
-
- # dnsmasq https
- if grep -q "^filter-rr=HTTPS" "/etc/dnsmasq.conf"; then
- log "Found and removed filter-rr=HTTPS in dnsmasq config"
- sed -i '/^filter-rr=HTTPS/d' "/etc/dnsmasq.conf"
- fi
-
- # dhcp use-application-dns.net
- if grep -q "use-application-dns.net" "/etc/config/dhcp"; then
- log "Found and removed use-application-dns.net in dhcp config"
- sed -i '/use-application-dns/d' "/etc/config/dhcp"
- fi
-}
-
-validate_service() {
- local domain="$1"
-
- for valid_service in $VALID_SERVICES; do
- if [ "$domain" = "$valid_service" ]; then
- return 0
- fi
- done
-
- log "Invalid service in domain_list: $domain. Exiting. Check config and LuCI cache"
- exit 1
-}
-
-process_validate_service() {
- config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
- if [ "$domain_list_enabled" -eq 1 ]; then
- config_list_foreach "$section" domain_list validate_service
- fi
-}
-
-# Main funcs
-
-route_table_rule_mark() {
- local table=podkop
-
- grep -q "105 $table" /etc/iproute2/rt_tables || echo "105 $table" >>/etc/iproute2/rt_tables
-
- if ! ip route list table $table | grep -q "local default dev lo scope host"; then
- log "Added route for tproxy"
- ip route add local 0.0.0.0/0 dev lo table $table
- else
- log "Route for tproxy exists"
- fi
-
- if ! ip rule list | grep -q "from all fwmark 0x105 lookup $table"; then
- log "Create marking rule"
- ip -4 rule add fwmark 0x105 table $table priority 105
- else
- log "Marking rule exist"
- fi
-}
-
-create_nft_table() {
- local table="PodkopTable"
-
- nft add table inet $table
- log "Create nft rules"
- nft add chain inet $table mangle { type filter hook prerouting priority -150 \; policy accept \;}
- nft add chain inet $table proxy { type filter hook prerouting priority -100 \; policy accept \;}
-
- nft add set inet $table podkop_subnets { type ipv4_addr\; flags interval\; auto-merge\; }
-
- nft add rule inet $table mangle iifname "br-lan" ip daddr @podkop_subnets meta l4proto tcp meta mark set 0x105 counter
- nft add rule inet $table mangle iifname "br-lan" ip daddr @podkop_subnets meta l4proto udp meta mark set 0x105 counter
- nft add rule inet $table mangle iifname "br-lan" ip daddr "$FAKEIP" meta l4proto tcp meta mark set 0x105 counter
- nft add rule inet $table mangle iifname "br-lan" ip daddr "$FAKEIP" meta l4proto udp meta mark set 0x105 counter
-
- nft add rule inet $table proxy meta mark 0x105 meta l4proto tcp tproxy ip to :1602 counter
- nft add rule inet $table proxy meta mark 0x105 meta l4proto udp tproxy ip to :1602 counter
-}
-
-save_dnsmasq_config() {
- local key="$1"
- local backup_key="$2"
- value=$(uci get "$key" 2>/dev/null)
-
- if [ -z "$value" ]; then
- uci set "$backup_key"="unset"
- else
- uci set "$backup_key"="$value"
- fi
-}
-
-dnsmasq_add_resolver() {
- log "Save dnsmasq config"
- save_dnsmasq_config "dhcp.@dnsmasq[0].noresolv" "dhcp.@dnsmasq[0].podkop_noresolv"
- save_dnsmasq_config "dhcp.@dnsmasq[0].cachesize" "dhcp.@dnsmasq[0].podkop_cachesize"
-
- uci -q delete dhcp.@dnsmasq[0].podkop_server
- for server in $(uci get dhcp.@dnsmasq[0].server 2>/dev/null); do
- if [[ "$server" == "127.0.0.42" ]]; then
- log "Dnsmasq save config error: server=127.0.0.42"
- else
- uci add_list dhcp.@dnsmasq[0].podkop_server="$server"
- fi
- done
-
- log "Configure dnsmasq for sing-box"
- uci set dhcp.@dnsmasq[0].noresolv="1"
- uci set dhcp.@dnsmasq[0].cachesize="0"
- uci -q delete dhcp.@dnsmasq[0].server
- uci add_list dhcp.@dnsmasq[0].server="127.0.0.42"
- uci commit dhcp
-
- /etc/init.d/dnsmasq restart
-}
-
-dnsmasq_restore() {
- log "Removing configuration for dnsmasq"
-
- local cachesize=$(uci get dhcp.@dnsmasq[0].podkop_cachesize 2>/dev/null)
- if [ -z "$cachesize" ]; then
- log "dnsmasq revert: cachesize is unset"
- else
- uci set dhcp.@dnsmasq[0].cachesize="$cachesize"
- fi
-
- local noresolv=$(uci get dhcp.@dnsmasq[0].podkop_noresolv 2>/dev/null)
- if [[ "$noresolv" == "unset" ]]; then
- log "dnsmasq revert: noresolv is unset"
- uci -q delete dhcp.@dnsmasq[0].noresolv
- else
- uci set dhcp.@dnsmasq[0].noresolv="$noresolv"
- fi
-
- local server=$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)
- if [[ "$server" == "127.0.0.42" ]]; then
- uci -q delete dhcp.@dnsmasq[0].server
- for server in $(uci get dhcp.@dnsmasq[0].podkop_server 2>/dev/null); do
- uci add_list dhcp.@dnsmasq[0].server="$server"
- done
- uci delete dhcp.@dnsmasq[0].podkop_server
- fi
-
- uci delete dhcp.@dnsmasq[0].podkop_cachesize
- uci delete dhcp.@dnsmasq[0].podkop_noresolv
-
- uci commit dhcp
-
- /etc/init.d/dnsmasq restart
-}
-
-process_domains_text() {
- local text="$1"
- local name="$2"
-
- local tmp_file=$(mktemp)
- echo "$text" > "$tmp_file"
-
- # First filter out full comment lines and remove comments after domains
- grep -v "^[[:space:]]*\/\/" "$tmp_file" | sed 's/\/\/.*$//' > "${tmp_file}.filtered"
-
- sed 's/[, ]\+/\n/g' "${tmp_file}.filtered" | while IFS= read -r domain; do
- domain=$(echo "$domain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
- if [ -n "$domain" ]; then
- sing_box_ruleset_domains "$domain" "$name"
- fi
- done
-
- rm -f "$tmp_file" "${tmp_file}.filtered"
-}
-
-process_subnets_text() {
- local text="$1"
- local name="$2"
-
- local tmp_file=$(mktemp)
- echo "$text" > "$tmp_file"
-
- # First filter out full comment lines and remove comments after subnets
- grep -v "^[[:space:]]*\/\/" "$tmp_file" | sed 's/\/\/.*$//' > "${tmp_file}.filtered"
-
- sed 's/[, ]\+/\n/g' "${tmp_file}.filtered" | while IFS= read -r subnet; do
- subnet=$(echo "$subnet" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
- if [ -n "$subnet" ]; then
- if ! echo "$subnet" | grep -q "/"; then
- subnet="$subnet/32"
- fi
- sing_box_ruleset_subnets "$subnet" "$name"
- fi
- done
-
- rm -f "$tmp_file" "${tmp_file}.filtered"
-}
-
-add_cron_job() {
- ## Future: make a check so that it doesn't recreate many times
- config_get domain_list_enabled "$section" "domain_list_enabled"
- config_get subnets_list_enabled "$section" "subnets_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"
- config_get update_interval "main" "update_interval"
-
- case "$update_interval" in
- "1h")
- cron_job="13 * * * * /etc/init.d/podkop list_update"
- ;;
- "3h")
- cron_job="13 */3 * * * /etc/init.d/podkop list_update"
- ;;
- "12h")
- cron_job="13 */12 * * * /etc/init.d/podkop list_update"
- ;;
- "1d")
- cron_job="13 9 * * * /etc/init.d/podkop list_update"
- ;;
- "3d")
- cron_job="13 9 */3 * * /etc/init.d/podkop list_update"
- ;;
- *)
- log "Invalid update_interval value: $update_interval"
- return
- ;;
- esac
-
- if [ "$domain_list_enabled" -eq 1 ] || [ "$subnets_list_enabled" -eq 1 ] ||
- [ "$custom_download_domains_list_enabled" -eq 1 ] || [ "$custom_download_subnets_list_enabled" -eq 1 ] ; then
- remove_cron_job
- crontab -l | {
- cat
- echo "$cron_job"
- } | crontab -
- log "The cron job has been created: $cron_job"
- fi
-}
-
-remove_cron_job() {
- (crontab -l | grep -v "/etc/init.d/podkop list_update") | crontab -
- log "The cron job removed"
-}
-
-list_update() {
- log "Update remote lists"
- config_foreach process_remote_ruleset
- config_foreach process_domains_list_url
- config_foreach process_subnet_for_section_remote
-}
-
-find_working_resolver() {
- local resolver_found=""
- for resolver in $DNS_RESOLVERS; do
- if nslookup $TEST_DOMAIN $resolver >/dev/null 2>&1; then
- echo "$resolver"
- return 0
- fi
- done
- echo "8.8.8.8"
- return 1
-}
-
-# sing-box funcs
-
-sing_box_uci() {
- local config="/etc/config/sing-box"
- if grep -q "option enabled '0'" "$config" ||
- grep -q "option user 'sing-box'" "$config"; then
- sed -i \
- -e "s/option enabled '0'/option enabled '1'/" \
- -e "s/option user 'sing-box'/option user 'root'/" $config
- log "Change sing-box UCI config"
- else
- log "Sing-box UCI config OK"
- fi
-}
-
-# Future: for every section. +1 port?
-process_socks5() {
- config_get_bool socks5 "main" "socks5" "0"
- if [ "$socks5" -eq 1 ]; then
- log "Socks5 local enable port 2080"
- jq '.inbounds += [{
- "tag": "mixed-in",
- "type": "mixed",
- "listen": "0.0.0.0",
- "listen_port": 2080,
- "set_system_proxy": false
- }]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
-
- #local rule_exists=$(jq -r '.route.rules[] | select(.inbound[] == "mixed-in")' $SING_BOX_CONFIG)
- local rule_exists=$(jq -r '.route.rules // [] | map(select(.inbound // [] | index("mixed-in"))) | length' $SING_BOX_CONFIG)
-
- if [ -z "$rule_exists" ]; then
- jq '.route.rules += [{
- "inbound": ["mixed-in"],
- "outbound": "main",
- "action": "route"
- }]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
- fi
- fi
-}
-
-sing_box_inbound_proxy() {
- local listen_port="$1"
-
- jq -n \
- --arg listen_port "$listen_port" \
- '{
- "log": {
- "level": "warn"
- },
- "inbounds": [
- {
- "tag": "tproxy-in",
- "type": "tproxy",
- "listen": "::",
- "listen_port": ($listen_port|tonumber),
- "tcp_fast_open": true,
- "udp_fragment": true
- },
- {
- "tag": "dns-in",
- "type": "direct",
- "listen": "127.0.0.42",
- "listen_port": 53
- }
- ],
- "outbounds": [
- {
- "tag": "direct-out",
- "type": "direct"
- }
- ]
- }' > $SING_BOX_CONFIG
-}
-
-sing_box_dns() {
- local dns_type
- local dns_server
- local resolver_tag="resolver"
-
- config_get dns_type "main" "dns_type" "doh"
- config_get dns_server "main" "dns_server" "1.1.1.1"
-
- local server_json
- local is_ip=$(echo "$dns_server" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' && echo "1" || echo "0")
-
- if [ "$is_ip" = "0" ]; then
- log "Finding working DNS resolver"
- local dns_resolver=$(find_working_resolver)
- log "Found working resolver: $dns_resolver"
- fi
-
- log "Configure DNS in sing-box"
-
- server_json=$(jq -n \
- --arg type "$dns_type" \
- --arg server "$dns_server" \
- --arg resolver "$resolver_tag" \
- --arg is_ip "$is_ip" \
- '{
- "servers": [
- {
- "tag": "dns-server",
- "address": (
- if $type == "doh" then
- "https://" + $server + "/dns-query"
- elif $type == "dot" then
- "tls://" + $server
- else
- $server
- end
- ),
- "detour": "direct-out"
- } + (
- if $is_ip == "0" then
- {"address_resolver": $resolver}
- else
- {}
- end
- )
- ]
- }')
-
- if [ "$is_ip" = "0" ]; then
- server_json=$(echo "$server_json" | jq \
- --arg resolver "$resolver_tag" \
- --arg address "$dns_resolver" \
- '.servers += [{
- "tag": $resolver,
- "address": $address
- }]')
- fi
-
- server_json=$(echo "$server_json" | jq '.servers += [{"tag": "fakeip-server", "address": "fakeip"}]')
-
- jq \
- --argjson dns_config "$server_json" \
- --arg fakeip "$FAKEIP" \
- '.dns = {
- "strategy": "ipv4_only",
- "fakeip": {
- "enabled": true,
- "inet4_range": $fakeip
- },
- "servers": $dns_config.servers
- }' $SING_BOX_CONFIG > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
-}
-
-sing_box_dns_rule_fakeip() {
- local rewrite_ttl
- config_get rewrite_ttl "main" "dns_rewrite_ttl" "600"
-
- log "Configure fakeip route in sing-box and set TTL to $rewrite_ttl seconds"
-
- jq \
- --arg ttl "$rewrite_ttl" \
- '.dns += {
- "rules": [
- {
- "query_type": [
- "HTTPS"
- ],
- "action": "reject"
- },
- {
- "domain_suffix": [
- "use-application-dns.net"
- ],
- "action": "reject"
- },
- {
- "server": "fakeip-server",
- "domain": "",
- "rewrite_ttl": ($ttl | tonumber),
- "rule_set": []
- }
- ]
- }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
-}
-
-sing_box_dns_rule_fakeip_section() {
- local rule_set=$1
- echo $rule_set
- log "Adding section to fakeip route rules in sing-box"
-
- jq \
- --arg rule_set "$rule_set" \
- '.dns.rules |= map(
- if .server == "fakeip-server" then
- if any(.rule_set[]?; . == $rule_set) then
- .
- else
- .rule_set += [$rule_set]
- end
- else
- .
- end
- )' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
-}
-
-sing_box_cache_file() {
- config_get cache_file "main" "cache_file" "/tmp/cache.db"
-
- log "Configure sing-box cache.db path"
-
- jq \
- --arg cache_file "$cache_file" \
- '.experimental = {
- "cache_file": {
- "enabled": true,
- "store_fakeip": true,
- "path": $cache_file
- }
- }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
-}
-
-sing_box_outdound() {
- local section="$1"
-
- config_get mode "$section" "mode"
- case "$mode" in
- "vpn")
- log "VPN mode"
- log "You are using VPN mode, make sure you have installed all the necessary packages and configured."
- config_get interface "$section" "interface"
- sing_box_outbound_interface $section $interface
- ;;
- "proxy")
- log "Proxy mode"
- config_get proxy_config_type "$section" "proxy_config_type"
-
- if [ "$proxy_config_type" = "outbound" ]; then
- config_get outbound_json $section "outbound_json"
- if [ -n "$outbound_json" ]; then
- log "Using JSON outbound configuration"
- sing_box_config_outbound_json "$outbound_json" "$section"
- else
- log "Missing outbound JSON configuration"
- return
- fi
- else
- config_get proxy_string $section "proxy_string"
-
- # Extract the first non-comment line as the active configuration
- active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1)
-
- if [ -z "$active_proxy_string" ]; then
- log "No active proxy configuration found"
- return
- fi
-
- if [[ "$active_proxy_string" =~ ^ss:// ]]; then
- sing_box_config_shadowsocks "$section" "$active_proxy_string"
- elif [[ "$active_proxy_string" =~ ^vless:// ]]; then
- sing_box_config_vless "$section" "$active_proxy_string"
- else
- log "Unsupported proxy type or missing configuration"
- return
- fi
- fi
- ;;
- *)
- log "Requires *vpn* or *proxy* value"
- return
- ;;
- esac
-}
-
-sing_box_outbound_interface() {
- local section="$1"
- local interface="$2"
-
- jq --arg section "$section" \
- --arg interface "$interface" \
- '. |
- .outbounds |= (
- map(
- if .tag == $section then
- . + {"type": "direct", "bind_interface": $interface}
- else . end
- ) +
- (
- if (map(select(.tag == $section)) | length) == 0 then
- [{"tag": $section, "type": "direct", "bind_interface": $interface}]
- else [] end
- )
- )' "$SING_BOX_CONFIG" > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
-
- if [ $? -eq 0 ]; then
- log "Config updated successfully"
- else
- log "Error: Invalid JSON config generated"
- return 1
- fi
-}
-
-sing_box_rule_dns() {
- log "Configure rule dns in sing-box"
- jq \
- '.route += {
- "rules": [
- {
- "inbound": [
- "dns-in",
- "tproxy-in"
- ],
- "action": "sniff"
- },
- {
- "protocol": "dns",
- "action": "hijack-dns"
- }
- ],
- "auto_detect_interface": true
- }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
-}
-
-sing_box_config_check() {
- if ! sing-box -c $SING_BOX_CONFIG check >/dev/null 2>&1; then
- log "Sing-box configuration is invalid"
- exit 1
- fi
-}
-
-sing_box_config_outbound_json() {
- local json_config="$1"
- local section="$2"
-
- # Create new object with tag first, then merge with the rest of the config
- local modified_config=$(echo "$json_config" | jq --arg section "$section" \
- 'del(.tag) | {"tag": $section} + .')
-
- jq --argjson outbound "$modified_config" \
- --arg section "$section" \
- '. |
- .outbounds |= (
- map(
- if .tag == $section then
- $outbound
- else . end
- ) +
- (
- if (map(select(.tag == $section)) | length) == 0 then
- [$outbound]
- else [] end
- )
- )' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
-
- if [ $? -eq 0 ]; then
- log "Outbound config updated successfully"
- else
- log "Error: Invalid JSON config generated"
- return 1
- fi
-}
-
-sing_box_config_shadowsocks() {
- local section="$1"
- local STRING="$2"
-
- if echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null | grep -q ":"; then
- local encrypted_part=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null )
- local method=$(echo "$encrypted_part" | cut -d':' -f1)
- local password=$(echo "$encrypted_part" | cut -d':' -f2-)
- else
- local method_and_password=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1)
- local method=$(echo "$method_and_password" | cut -d':' -f1)
- local password=$(echo "$method_and_password" | cut -d':' -f2- | sed 's/%3D/=/g')
- if echo "$method" | base64 -d ; then
- method=$(echo "$method" | base64 -d)
- fi
- fi
-
- local server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1)
- local port=$(echo "$STRING" | sed -n 's|.*:\([0-9]\+\).*|\1|p')
-
- jq \
- --arg section "$section" \
- --arg server "$server" \
- --argjson port "$port" \
- --arg method "$method" \
- --arg password "$password" \
- '. |
- .outbounds |= (
- map(
- if .tag == $section then
- . + {
- "type": "shadowsocks",
- "server": $server,
- "server_port": ($port | tonumber),
- "method": $method,
- "password": $password,
- "udp_over_tcp": { "enabled": true, "version": 2 }
- }
- else . end
- ) +
- (
- if (map(select(.tag == $section)) | length) == 0 then
- [{
- "tag": $section,
- "type": "shadowsocks",
- "server": $server,
- "server_port": ($port | tonumber),
- "method": $method,
- "password": $password,
- "udp_over_tcp": { "enabled": true, "version": 2 }
- }]
- else [] end
- )
- )' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
-
- if [ $? -eq 0 ]; then
- log "Config updated successfully"
- else
- log "Error: Invalid JSON config generated"
- return 1
- fi
-}
-
-sing_box_config_vless() {
- local section="$1"
- local STRING="$2"
-
- get_param() {
- local param="$1"
- local value=$(echo "$STRING" | sed -n "s/.*[?&]$param=\([^&?#]*\).*/\1/p")
- value=$(echo "$value" | sed 's/%2F/\//g; s/%2C/,/g; s/%3D/=/g; s/%2B/+/g; s/%20/ /g; s/%3B/;/g' | tr -d '\n' | tr -d '\r')
- echo "$value"
- }
-
- uuid=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
- server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
- port=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f2 | cut -d'?' -f1 | cut -d'/' -f1 | cut -d'#' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
-
- jq \
- --arg server "$server" \
- --argjson port "$port" \
- --arg uuid "$uuid" \
- --arg type "$(get_param "type")" \
- --arg flow "$(get_param "flow")" \
- --arg sni "$(get_param "sni")" \
- --arg fp "$(get_param "fp")" \
- --arg security "$(get_param "security")" \
- --arg pbk "$(get_param "pbk")" \
- --arg sid "$(get_param "sid")" \
- --arg alpn "$(get_param "alpn")" \
- --arg path "$(get_param "path")" \
- --arg host "$(get_param "host")" \
- --arg spx "$(get_param "spx")" \
- --arg insecure "$(get_param "allowInsecure")" \
- --arg section "$section" \
- '. |
- # Updating an existing outbound by tag or adding a new one
- .outbounds |= (
- # If an element with the required tag is found, update it
- map(
- if .tag == $section then
- . + {
- "type": "vless",
- "server": $server,
- "server_port": ($port | tonumber),
- "uuid": $uuid,
- "packet_encoding": "",
- "domain_strategy": "",
- "flow": $flow
- }
- else . end
- ) +
- # Add a new outbound if the required tag is not present
- (
- if (map(select(.tag == $section)) | length) == 0 then
- [{
- "tag": $section,
- "type": "vless",
- "server": $server,
- "server_port": ($port | tonumber),
- "uuid": $uuid,
- "packet_encoding": "",
- "domain_strategy": "",
- "flow": $flow
- }]
- else [] end
- )
- ) |
- # Additional parameters such as transport and tls
- if $flow != "" then
- .outbounds |= map(
- if .tag == $section then
- .flow = $flow
- else . end
- )
- else . end |
- if $type == "ws" then
- .outbounds |= map(
- if .tag == $section then
- .transport = {
- "type": "ws",
- "path": $path
- } |
- if $host != "" then
- .transport.headers = { "Host": $host }
- else . end
- else . end
- )
- elif $type == "grpc" then
- .outbounds |= map(
- if .tag == $section then
- .transport = { "type": "grpc" }
- else . end
- )
- else . end |
- if $security == "reality" or $security == "tls" then
- .outbounds |= map(
- if .tag == $section then
- .tls = {
- "enabled": true,
- "server_name": $sni,
- "utls": {
- "enabled": true,
- "fingerprint": $fp
- },
- "insecure": ($insecure == "1")
- } |
- if $alpn != "" then
- .tls.alpn = ($alpn | split(","))
- else . end |
- if $security == "reality" then
- .tls.reality = {
- "enabled": true,
- "public_key": $pbk,
- "short_id": $sid
- }
- else . end
- else . end
- )
- else . end' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
-
-
- if [ $? -eq 0 ]; then
- log "Config created successfully"
- else
- log "Error: Invalid JSON config generated"
- return 1
- fi
-}
-
-# Process. Sing-box rules
-
-sing_box_ruleset_domains() {
- log "Configure ruleset domains in sing-box"
-
- local domain=$1
- local tag=$2
-
- # Check if there is a route.rule_set for the specified tag
- local tag_exists=$(jq -r --arg tag "$tag" '
- .route.rule_set[]? | select(.tag == $tag) | .tag
- ' /etc/sing-box/config.json)
-
- # If the tag exists, add the domain
- if [[ -n "$tag_exists" ]]; then
- jq \
- --arg tag "$tag" \
- --arg domain "$domain" \
- '
- .route.rule_set[] |=
- if .tag == $tag then
- .rules[0].domain_suffix += [$domain]
- else
- .
- end
- ' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
-
- log "$domain added to the list for tag $tag"
- else
- # If tag does not exist, add a new set of rules
- jq \
- --arg tag "$tag" \
- --arg domain "$domain" \
- '
- .route.rule_set += [
- {
- "tag": $tag,
- "type": "inline",
- "rules": [
- {
- "domain_suffix": [$domain]
- }
- ]
- }
- ]' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
-
- log "$domain added as a new rule set for tag $tag"
- fi
-}
-
-sing_box_ruleset_subnets() {
- log "Configure ruleset domains in sing-box"
-
- local subnet=$1
- local tag=$2
-
- # nft
- nft add element inet PodkopTable podkop_subnets { $subnet }
-
- # Check if there is a route.rule_set for the specified tag
- local tag_exists=$(jq -r --arg tag "$tag" '
- .route.rule_set[]? | select(.tag == $tag) | .tag
- ' /etc/sing-box/config.json)
-
- # If tag exists, add the domain
- if [[ -n "$tag_exists" ]]; then
- jq \
- --arg tag "$tag" \
- --arg subnet "$subnet" \
- '
- .route.rule_set[] |=
- if .tag == $tag then
- .rules[0].ip_cidr += [$subnet]
- else
- .
- end
- ' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
-
- log "$subnet added to the list for tag $tag"
- else
- # If tag does not exist, add a new set of rules
- jq \
- --arg tag "$tag" \
- --arg subnet "$subnet" \
- '
- .route.rule_set += [
- {
- "tag": $tag,
- "type": "inline",
- "rules": [
- {
- "ip_cidr": [$subnet]
- }
- ]
- }
- ]' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
-
- log "$subnet added as a new rule set for tag $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"
- if [ "$custom_domains_list_type" = "dynamic" ]; then
- # Handle list domains from custom_domains
- config_list_foreach "$section" custom_domains "sing_box_ruleset_domains" "$section"
- elif [ "$custom_domains_list_type" = "text" ]; then
- # Handle domains from text
- config_get custom_domains_text "$section" "custom_domains_text"
- process_domains_text "$custom_domains_text" "$section"
- fi
- fi
-}
-
-sing_box_ruleset_remote() {
- local tag=$1
- local type=$2
- local update_interval=$3
-
- 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" \
- '
- .route.rule_set += [
- {
- "tag": $tag,
- "type": $type,
- "format": "binary",
- "url": $url,
- "update_interval": $update_interval
- }
- ]' "$SING_BOX_CONFIG" > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$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
- ;;
- "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 "br-lan" ip daddr @podkop_discord_subnets udp dport { 50000-65535 } meta mark set 0x105 counter
- ;;
- *)
- return
- ;;
- esac
-
- local filename=$(basename "$URL")
- wget -O "/tmp/podkop/$filename" "$URL"
-
- 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"
- local outbound="$2"
-
- # Check if there is an outbound rule for "tproxy-in"
- local rule_exists=$(jq -r '.route.rules[] | select(.outbound == "'"$outbound"'" and .inbound == ["tproxy-in"])' "$SING_BOX_CONFIG")
-
- if [[ -n "$rule_exists" ]]; then
- # If a rule for tproxy-in exists, add a new rule_set to the existing rule
- jq \
- --arg rule_set "$rule_set" \
- --arg outbound "$outbound" \
- '(.route.rules[] | select(.outbound == $outbound and .inbound == ["tproxy-in"]) .rule_set) += [$rule_set]' \
- "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
- else
- # If there is no rule for tproxy-in, create a new one with rule_set
- jq \
- --arg rule_set "$rule_set" \
- --arg outbound "$outbound" \
- '.route.rules += [{
- "inbound": ["tproxy-in"],
- "rule_set": [$rule_set],
- "outbound": $outbound,
- "action": "route"
- }]' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
- fi
-}
-
-sing_box_quic_reject() {
- local quic_rule_exists=$(jq -e '.route.rules[] | select(.protocol == "quic" and .action == "reject")' "$SING_BOX_CONFIG")
-
- if [[ -z "$quic_rule_exists" ]]; then
- jq '
- .route.rules |= (
- reduce .[] as $rule ([];
- if $rule.protocol == "dns" and $rule.action == "hijack-dns" then
- . + [$rule, {"protocol": "quic", "action": "reject"}]
- else
- . + [$rule]
- end
- )
- )' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
-
- log "QUIC reject rule added successfully"
- fi
-}
-
-process_remote_ruleset() {
- 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 "sing_box_ruleset_remote" "remote" "1d"
- config_list_foreach "$section" domain_list "list_subnets_download" "$section" "$domain_list"
- fi
-}
-
-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" ] || [ "$custom_download_domains_list_enabled" = "1" ] ||
- [ "$custom_download_subnets_list_enabled" = "1" ]; then
- sing_box_rules "$section" "$section"
- fi
-
- if [ "$custom_domains_list_type" != "disabled" ] || [ "$custom_local_domains_list_enabled" = "1" ] ||
- [ "$custom_download_domains_list_enabled" = "1" ]; then
- sing_box_dns_rule_fakeip_section "$section" "$section"
- fi
-
- config_get domain_list_enabled "$section" "domain_list_enabled"
- config_get domain_list "$section" "domain_list"
- if [ "$domain_list_enabled" -eq 1 ]; then
- config_list_foreach $section domain_list sing_box_rules $section
- config_list_foreach $section domain_list sing_box_dns_rule_fakeip_section domain_list
- fi
-}
-
-list_custom_local_domains_create() {
- local section="$2"
- local local_file="$1"
- local filename=$(basename "$local_file" | cut -d. -f1)
-
- while IFS= read -r domain; do
- domain=$(echo "$domain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
- if [ -n "$domain" ] && echo "$domain" | grep -E -q '^([a-zA-Z0-9][-a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$'; then
- log "Added $domain from local file"
- sing_box_ruleset_domains "$domain" "$section"
- else
- log "Invalid domain skipped: $domain"
- fi
- done <"$local_file"
-}
-
-process_domains_list_local() {
- local section="$1"
-
- config_get custom_local_domains_list_enabled "$section" "custom_local_domains_list_enabled"
- if [ "$custom_local_domains_list_enabled" -eq 1 ]; then
- log "Adding a custom domains list from file in $section"
- config_list_foreach "$section" "custom_local_domains" list_custom_local_domains_create "$section"
- fi
-}
-
-list_custom_url_domains_create() {
- local section="$2"
- local URL="$1"
- local filename=$(basename "$URL")
-
- wget -q -O "/tmp/podkop/${filename}" "$URL"
-
- while IFS= read -r domain; do
- log "From local file: $domain"
- sing_box_ruleset_domains $domain $section
- done <"/tmp/podkop/$filename"
-}
-
-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"
- if [ "$custom_subnets_list_enabled" = "dynamic" ]; then
- # Handle list domains from custom_domains
- config_list_foreach "$section" custom_subnets "sing_box_ruleset_subnets" "$section"
- elif [ "$custom_subnets_list_enabled" = "text" ]; then
- # Handle domains from text
- config_get custom_subnets_text "$section" "custom_subnets_text"
- process_subnets_text "$custom_subnets_text" "$section"
- fi
- fi
-}
-
-list_custom_url_subnets_create() {
- local section="$2"
- local URL="$1"
- local filename=$(basename "$URL")
-
- wget -q -O "/tmp/podkop/${filename}" "$URL"
-
- while IFS= read -r subnet; do
- log "From local file: $subnet"
- sing_box_ruleset_subnets $subnet $section
- done <"/tmp/podkop/$filename"
-}
-
-process_subnet_for_section_remote() {
- 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"
- fi
-}
-
-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 sing_box_rules_source_ip_cidr $all_traffic_ip $section
- 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)
-
-
- 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 >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
- else
- jq \
- --arg source_ip_cidr "$source_ip_cidr" \
- --arg outbound "$outbound" \
- '.route.rules = [
- {
- "inbound": ["tproxy-in"],
- "source_ip_cidr": [$source_ip_cidr],
- "outbound": $outbound,
- "action": "route"
- }
- ] + .route.rules' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
- fi
-}
-
-## nftables
-list_all_traffic_from_ip() {
- local ip="$1"
- if ! nft list chain inet PodkopTable mangle | grep -q "ip saddr $ip"; then
- nft add set inet PodkopTable localv4 { type ipv4_addr\; flags interval\; }
- nft add element inet PodkopTable localv4 { \
- 0.0.0.0/8, \
- 10.0.0.0/8, \
- 127.0.0.0/8, \
- 169.254.0.0/16, \
- 172.16.0.0/12, \
- 192.0.0.0/24, \
- 192.0.2.0/24, \
- 192.88.99.0/24, \
- 192.168.0.0/16, \
- 198.51.100.0/24, \
- 203.0.113.0/24, \
- 224.0.0.0/4, \
- 240.0.0.0-255.255.255.255 }
- nft insert rule inet PodkopTable mangle iifname "br-lan" ip saddr $ip meta l4proto { tcp, udp } meta mark set 0x105 counter
- nft insert rule inet PodkopTable mangle ip saddr $ip ip daddr @localv4 return
- fi
-}
-
-# Diagnotics
-check_proxy() {
- if ! command -v sing-box >/dev/null 2>&1; then
- nolog "sing-box is not installed"
- return 1
- fi
-
- if [ ! -f $SING_BOX_CONFIG ]; then
- nolog "Configuration file not found"
- return 1
- fi
-
- nolog "Checking sing-box configuration..."
-
- if ! sing-box -c $SING_BOX_CONFIG check >/dev/null; then
- nolog "Invalid configuration"
- return 1
- fi
-
- jq '
- walk(
- if type == "object" then
- with_entries(
- if .key == "uuid" then
- .value = "MASKED"
- elif .key == "server" then
- .value = "MASKED"
- elif .key == "server_name" then
- .value = "MASKED"
- elif .key == "password" then
- .value = "MASKED"
- elif .key == "public_key" then
- .value = "MASKED"
- elif .key == "short_id" then
- .value = "MASKED"
- elif .key == "fingerprint" then
- .value = "MASKED"
- elif .key == "server_port" then
- .value = "MASKED"
- else . end
- )
- else . end
- )' $SING_BOX_CONFIG
-
- nolog "Checking proxy connection..."
-
-
- for attempt in `seq 1 5`; do
- response=$(sing-box tools fetch ifconfig.me -D /etc/sing-box 2>/dev/null)
- if echo "$response" | grep -q "^/dev/null 2>&1; then
- nolog "nft is not installed"
- return 1
- fi
-
- nolog "Checking PodkopTable rules..."
-
- # Check if table exists
- if ! nft list table inet PodkopTable >/dev/null 2>&1; then
- nolog "PodkopTable not found"
- return 1
- fi
-
- # Get all sets
- nolog "\nSets configuration:"
-
- nft list table inet PodkopTable
-
- nolog "\nNFT check completed"
-}
-
-check_github() {
- nolog "Checking GitHub connectivity..."
-
- if ! curl -m 3 github.com; then
- nolog "Error: Cannot connect to GitHub"
- return 1
- fi
- nolog "GitHub is accessible"
-
- nolog "Checking lists availability:"
- for url in "$DOMAINS_RU_INSIDE" "$DOMAINS_RU_OUTSIDE" "$DOMAINS_UA" "$DOMAINS_YOUTUBE" \
- "$SUBNETS_TWITTER" "$SUBNETS_META" "$SUBNETS_DISCORD"; do
- local list_name=$(basename "$url")
- wget -q -O /dev/null "$url"
- if [ $? -eq 0 ]; then
- nolog "- $list_name: available"
- else
- nolog "- $list_name: not available"
- fi
- done
-}
-
-check_dnsmasq() {
- nolog "Checking dnsmasq configuration..."
-
- local config=$(uci show dhcp.@dnsmasq[0])
- if [ -z "$config" ]; then
- nolog "No dnsmasq configuration found"
- return 1
- fi
-
- echo "$config" | while IFS='=' read -r key value; do
- nolog "$key = $value"
- done
-}
-
-check_sing_box_connections() {
- nolog "Checking sing-box connections..."
-
- if ! command -v netstat >/dev/null 2>&1; then
- nolog "netstat is not installed"
- return 1
- fi
-
- local connections=$(netstat -tuanp | grep sing-box)
- if [ -z "$connections" ]; then
- nolog "No active sing-box connections found"
- return 1
- fi
-
- echo "$connections" | while read -r line; do
- nolog "$line"
- done
-}
-
-check_sing_box_logs() {
- nolog "Showing sing-box logs from system journal..."
-
- local logs=$(logread -e sing-box | tail -n 50)
- if [ -z "$logs" ]; then
- nolog "No sing-box logs found"
- return 1
- fi
-
- echo "$logs"
-}
-
-check_logs() {
- nolog "Showing podkop logs from system journal..."
-
- if command -v logread >/dev/null 2>&1; then
- logread -e podkop | tail -n 50
- else
- nolog "Error: logread command not found"
- return 1
- fi
-}
-
-show_sing_box_config() {
- nolog "Current sing-box configuration:"
-
- if [ ! -f "$SING_BOX_CONFIG" ]; then
- nolog "Configuration file not found"
- return 1
- fi
-
- jq '
- walk(
- if type == "object" then
- with_entries(
- if .key == "uuid" then
- .value = "MASKED"
- elif .key == "server" then
- .value = "MASKED"
- elif .key == "server_name" then
- .value = "MASKED"
- elif .key == "password" then
- .value = "MASKED"
- elif .key == "public_key" then
- .value = "MASKED"
- elif .key == "short_id" then
- .value = "MASKED"
- elif .key == "fingerprint" then
- .value = "MASKED"
- elif .key == "server_port" then
- .value = "MASKED"
- else . end
- )
- else . end
- )' "$SING_BOX_CONFIG"
-}
-
-show_config() {
- nolog "Current podkop configuration:"
-
- if [ ! -f /etc/config/podkop ]; then
- nolog "Configuration file not found"
- return 1
- fi
-
- tmp_config=$(mktemp)
-
- cat /etc/config/podkop | sed \
- -e 's/\(option proxy_string\).*/\1 '\''MASKED'\''/g' \
- -e 's/\(option outbound_json\).*/\1 '\''MASKED'\''/g' \
- -e 's/\(option second_proxy_string\).*/\1 '\''MASKED'\''/g' \
- -e 's/\(option second_outbound_json\).*/\1 '\''MASKED'\''/g' \
- -e 's/\(vless:\/\/[^@]*@\)/vless:\/\/MASKED@/g' \
- -e 's/\(ss:\/\/[^@]*@\)/ss:\/\/MASKED@/g' \
- -e 's/\(pbk=[^&]*\)/pbk=MASKED/g' \
- -e 's/\(sid=[^&]*\)/sid=MASKED/g' \
- > "$tmp_config"
-
- cat "$tmp_config"
- rm -f "$tmp_config"
-}
-
-show_version() {
- local version=$(opkg info podkop | grep -m 1 "Version:" | cut -d' ' -f2)
- echo "$version"
-}
-
-show_luci_version() {
- local version=$(opkg info luci-app-podkop | grep -m 1 "Version:" | cut -d' ' -f2)
- echo "$version"
-}
-
-show_sing_box_version() {
- local version=$(opkg info sing-box | grep -m 1 "Version:" | cut -d' ' -f2)
- echo "$version"
-}
-
-show_system_info() {
- echo "=== OpenWrt Version ==="
- grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2
- echo
- echo "=== Device Model ==="
- cat /tmp/sysinfo/model
-}
-
-get_sing_box_status() {
- local running=0
- local enabled=0
- local status=""
- local version=""
- local dns_configured=0
-
- # Check if service is enabled
- if [ -x /etc/rc.d/S99sing-box ]; then
- enabled=1
- fi
-
- # Check if service is running
- if pgrep -f "sing-box" >/dev/null; then
- running=1
- version=$(sing-box version | head -n 1 | awk '{print $3}')
- fi
-
- # Check DNS configuration
- local dns_server=$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)
- if [ "$dns_server" = "127.0.0.42" ]; then
- dns_configured=1
- fi
-
- # Format status message
- if [ $running -eq 1 ]; then
- if [ $enabled -eq 1 ]; then
- status="running & enabled"
- else
- status="running but disabled"
- fi
- else
- if [ $enabled -eq 1 ]; then
- status="stopped but enabled"
- else
- status="stopped & disabled"
- fi
- fi
-
- echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\",\"dns_configured\":$dns_configured}"
-}
-
-get_status() {
- local running=0
- local enabled=0
- local status=""
-
- # Check if service is enabled
- if [ -x /etc/rc.d/S99podkop ]; then
- enabled=1
- fi
-
- # Check if service is running
- if pgrep -f "sing-box" >/dev/null; then
- running=1
- fi
-
- # Format status message
- if [ $running -eq 1 ]; then
- if [ $enabled -eq 1 ]; then
- status="running & enabled"
- else
- status="running but disabled"
- fi
- else
- if [ $enabled -eq 1 ]; then
- status="stopped but enabled"
- else
- status="stopped & disabled"
- fi
- fi
-
- echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}"
-}
-
-sing_box_add_secure_dns_probe_domain() {
- local domain="httpbin.org"
- local override_address="numbersapi.com"
-
- if [ -z "$override_address" ]; then
- log "Error: Could not get br-lan IP address"
- return 1
- fi
-
- log "Adding DNS probe domain ${domain} to fakeip-server configuration"
-
- jq \
- --arg domain "$domain" \
- --arg override "$override_address" \
- '.dns.rules |= map(
- if .server == "fakeip-server" then
- . + {
- "domain": $domain
- }
- else
- .
- end
- ) |
- .route.rules |= . + [
- {
- "domain": $domain,
- "action": "route-options",
- "override_address": $override
- }
- ]' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
-
- log "DNS probe domain ${domain} configured with override to ${override_address}"
}
\ No newline at end of file
diff --git a/podkop/files/usr/bin/podkop b/podkop/files/usr/bin/podkop
new file mode 100755
index 0000000..89dd8de
--- /dev/null
+++ b/podkop/files/usr/bin/podkop
@@ -0,0 +1,1848 @@
+#!/bin/ash
+
+[ -r /lib/functions.sh ] && . /lib/functions.sh
+[ -r /lib/config/uci.sh ] && . /lib/config/uci.sh
+
+config_load "/etc/config/podkop"
+
+GITHUB_RAW_URL="https://raw.githubusercontent.com/itdoginfo/allow-domains/main"
+SRS_MAIN_URL="https://github.com/itdoginfo/allow-domains/releases/latest/download"
+DOMAINS_RU_INSIDE="${GITHUB_RAW_URL}/Russia/inside-dnsmasq-nfset.lst"
+DOMAINS_RU_OUTSIDE="${GITHUB_RAW_URL}/Russia/outside-dnsmasq-nfset.lst"
+DOMAINS_UA="${GITHUB_RAW_URL}/Ukraine/inside-dnsmasq-nfset.lst"
+DOMAINS_YOUTUBE="${GITHUB_RAW_URL}/Services/youtube.lst"
+SUBNETS_TWITTER="${GITHUB_RAW_URL}/Subnets/IPv4/twitter.lst"
+SUBNETS_META="${GITHUB_RAW_URL}/Subnets/IPv4/meta.lst"
+SUBNETS_DISCORD="${GITHUB_RAW_URL}/Subnets/IPv4/discord.lst"
+SUBNETS_TELERAM="${GITHUB_RAW_URL}/Subnets/IPv4/telegram.lst"
+SING_BOX_CONFIG="/etc/sing-box/config.json"
+FAKEIP="198.18.0.0/15"
+VALID_SERVICES="russia_inside russia_outside ukraine_inside geoblock block porn news anime youtube discord meta twitter hdrezka tiktok telegram"
+DNS_RESOLVERS="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 9.9.9.11 94.140.14.14 94.140.15.15 208.67.220.220 208.67.222.222 77.88.8.1 77.88.8.8"
+TEST_DOMAIN="google.com"
+
+log() {
+ local message="$1"
+ local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
+ local CYAN="\033[0;36m"
+ local GREEN="\033[0;32m"
+ local RESET="\033[0m"
+
+ echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}"
+ logger -t "podkop" "$timestamp $message"
+}
+
+nolog() {
+ local message="$1"
+ local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
+ local CYAN="\033[0;36m"
+ local GREEN="\033[0;32m"
+ local RESET="\033[0m"
+
+ echo -e "${CYAN}[$timestamp]${RESET} ${GREEN}$message${RESET}"
+}
+
+start() {
+ migration
+
+ config_foreach process_validate_service
+
+ sleep 3
+
+ mkdir -p /tmp/podkop
+
+ # base
+ route_table_rule_mark
+ create_nft_table
+ sing_box_uci
+
+ # sing-box
+ sing_box_inbound_proxy 1602
+ sing_box_dns
+ sing_box_dns_rule_fakeip
+ sing_box_rule_dns
+ sing_box_add_secure_dns_probe_domain
+ sing_box_cache_file
+ process_socks5
+
+ # sing-box outbounds and rules
+ config_foreach sing_box_outdound
+ config_foreach process_domains_for_section
+ config_foreach process_remote_ruleset
+ config_foreach sing_box_rule_preset
+ config_foreach process_domains_list_local
+ config_foreach process_domains_list_url
+ config_foreach process_subnet_for_section
+ config_foreach process_subnet_for_section_remote
+ config_foreach process_all_traffic_for_section
+ config_foreach add_cron_job
+
+ # Future: exclude at the fakeip?
+ config_get_bool exclude_from_ip_enabled "main" "exclude_from_ip_enabled" "0"
+ if [ "$exclude_from_ip_enabled" -eq 1 ]; then
+ log "Adding an IP for exclusion"
+ config_list_foreach main exclude_traffic_ip sing_box_rules_source_ip_cidr $exclude_traffic_ip direct-out
+ fi
+
+ config_get_bool yacd "main" "yacd" "0"
+ if [ "$yacd" -eq 1 ]; then
+ log "Yacd enable"
+ jq '.experimental.clash_api = {
+ "external_ui": "ui",
+ "external_controller": "0.0.0.0:9090"
+ }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+ fi
+
+ config_get_bool exclude_ntp "main" "exclude_ntp" "0"
+ if [ "$exclude_ntp" -eq 1 ]; then
+ log "NTP traffic exclude for proxy"
+ nft insert rule inet PodkopTable mangle udp dport 123 return
+ fi
+
+ config_get_bool quic_disable "main" "quic_disable" "0"
+ if [ "$quic_disable" -eq 1 ]; then
+ log "Rule for disable QUIC"
+ sing_box_quic_reject
+ fi
+
+ sing_box_config_check
+ /etc/init.d/sing-box restart
+ /etc/init.d/sing-box enable
+
+ config_get proxy_string "main" "proxy_string"
+ config_get interface "main" "interface"
+ config_get outbound_json "main" "outbound_json"
+
+ if [ -n "$proxy_string" ] || [ -n "$interface" ] || [ -n "$outbound_json" ]; then
+ config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0"
+ if [ "$dont_touch_dhcp" -eq 0 ]; then
+ dnsmasq_add_resolver
+ fi
+ fi
+}
+
+stop() {
+ log "Stopping the podkop"
+ remove_cron_job
+
+ config_get_bool dont_touch_dhcp "main" "dont_touch_dhcp" "0"
+ if [ "$dont_touch_dhcp" -eq 0 ]; then
+ dnsmasq_restore
+ fi
+
+ rm -rf /tmp/podkop/*.lst
+
+ log "Flush nft"
+ if nft list table inet PodkopTable >/dev/null 2>&1; then
+ nft delete table inet PodkopTable
+ fi
+
+ log "Flush ip rule"
+ if ip rule list | grep -q "podkop"; then
+ ip rule del fwmark 0x105 table podkop priority 105
+ fi
+
+ log "Flush ip route"
+ if ip route list table podkop >/dev/null 2>&1; then
+ ip route flush table podkop
+ fi
+
+ log "Stop sing-box"
+ /etc/init.d/sing-box stop
+ /etc/init.d/sing-box disable
+
+}
+
+# Migrations and validation funcs
+migration() {
+ # list migrate
+ local CONFIG="/etc/config/podkop"
+
+ if grep -q "ru_inside" $CONFIG; then
+ log "Depricated list found: ru_inside"
+ sed -i '/ru_inside/d' $CONFIG
+ fi
+
+ if grep -q "list domain_list 'ru_outside'" $CONFIG; then
+ log "Depricated list found: sru_outside"
+ sed -i '/ru_outside/d' $CONFIG
+ fi
+
+ if grep -q "list domain_list 'ua'" $CONFIG; then
+ log "Depricated list found: ua"
+ sed -i '/ua/d' $CONFIG
+ fi
+
+ # Subnet list
+ if grep -q "list subnets" $CONFIG; then
+ log "Depricated second section found"
+ sed -i '/list subnets/d' $CONFIG
+ fi
+
+ # second remove
+ if grep -q "config second 'second'" $CONFIG; then
+ log "Depricated second section found"
+ sed -i '/second/d' $CONFIG
+ fi
+
+ # cron update
+ if grep -qE "^\s*option update_interval '[0-9*/,-]+( [0-9*/,-]+){4}'" $CONFIG; then
+ log "Depricated update_interval"
+ sed -i "s|^\(\s*option update_interval\) '[0-9*/,-]\+\( [0-9*/,-]\+\)\{4\}'|\1 '1d'|" $CONFIG
+ fi
+
+ # dnsmasq https
+ if grep -q "^filter-rr=HTTPS" "/etc/dnsmasq.conf"; then
+ log "Found and removed filter-rr=HTTPS in dnsmasq config"
+ sed -i '/^filter-rr=HTTPS/d' "/etc/dnsmasq.conf"
+ fi
+
+ # dhcp use-application-dns.net
+ if grep -q "use-application-dns.net" "/etc/config/dhcp"; then
+ log "Found and removed use-application-dns.net in dhcp config"
+ sed -i '/use-application-dns/d' "/etc/config/dhcp"
+ fi
+}
+
+validate_service() {
+ local domain="$1"
+
+ for valid_service in $VALID_SERVICES; do
+ if [ "$domain" = "$valid_service" ]; then
+ return 0
+ fi
+ done
+
+ log "Invalid service in domain_list: $domain. Exiting. Check config and LuCI cache"
+ exit 1
+}
+
+process_validate_service() {
+ config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
+ if [ "$domain_list_enabled" -eq 1 ]; then
+ config_list_foreach "$section" domain_list validate_service
+ fi
+}
+
+# Main funcs
+
+route_table_rule_mark() {
+ local table=podkop
+
+ grep -q "105 $table" /etc/iproute2/rt_tables || echo "105 $table" >>/etc/iproute2/rt_tables
+
+ if ! ip route list table $table | grep -q "local default dev lo scope host"; then
+ log "Added route for tproxy"
+ ip route add local 0.0.0.0/0 dev lo table $table
+ else
+ log "Route for tproxy exists"
+ fi
+
+ if ! ip rule list | grep -q "from all fwmark 0x105 lookup $table"; then
+ log "Create marking rule"
+ ip -4 rule add fwmark 0x105 table $table priority 105
+ else
+ log "Marking rule exist"
+ fi
+}
+
+create_nft_table() {
+ local table="PodkopTable"
+
+ nft add table inet $table
+ log "Create nft rules"
+ nft add chain inet $table mangle { type filter hook prerouting priority -150 \; policy accept \;}
+ nft add chain inet $table proxy { type filter hook prerouting priority -100 \; policy accept \;}
+
+ nft add set inet $table podkop_subnets { type ipv4_addr\; flags interval\; auto-merge\; }
+
+ nft add rule inet $table mangle iifname "br-lan" ip daddr @podkop_subnets meta l4proto tcp meta mark set 0x105 counter
+ nft add rule inet $table mangle iifname "br-lan" ip daddr @podkop_subnets meta l4proto udp meta mark set 0x105 counter
+ nft add rule inet $table mangle iifname "br-lan" ip daddr "$FAKEIP" meta l4proto tcp meta mark set 0x105 counter
+ nft add rule inet $table mangle iifname "br-lan" ip daddr "$FAKEIP" meta l4proto udp meta mark set 0x105 counter
+
+ nft add rule inet $table proxy meta mark 0x105 meta l4proto tcp tproxy ip to :1602 counter
+ nft add rule inet $table proxy meta mark 0x105 meta l4proto udp tproxy ip to :1602 counter
+}
+
+save_dnsmasq_config() {
+ local key="$1"
+ local backup_key="$2"
+ value=$(uci get "$key" 2>/dev/null)
+
+ if [ -z "$value" ]; then
+ uci set "$backup_key"="unset"
+ else
+ uci set "$backup_key"="$value"
+ fi
+}
+
+dnsmasq_add_resolver() {
+ log "Save dnsmasq config"
+ save_dnsmasq_config "dhcp.@dnsmasq[0].noresolv" "dhcp.@dnsmasq[0].podkop_noresolv"
+ save_dnsmasq_config "dhcp.@dnsmasq[0].cachesize" "dhcp.@dnsmasq[0].podkop_cachesize"
+
+ uci -q delete dhcp.@dnsmasq[0].podkop_server
+ for server in $(uci get dhcp.@dnsmasq[0].server 2>/dev/null); do
+ if [[ "$server" == "127.0.0.42" ]]; then
+ log "Dnsmasq save config error: server=127.0.0.42"
+ else
+ uci add_list dhcp.@dnsmasq[0].podkop_server="$server"
+ fi
+ done
+
+ log "Configure dnsmasq for sing-box"
+ uci set dhcp.@dnsmasq[0].noresolv="1"
+ uci set dhcp.@dnsmasq[0].cachesize="0"
+ uci -q delete dhcp.@dnsmasq[0].server
+ uci add_list dhcp.@dnsmasq[0].server="127.0.0.42"
+ uci commit dhcp
+
+ /etc/init.d/dnsmasq restart
+}
+
+dnsmasq_restore() {
+ log "Removing configuration for dnsmasq"
+
+ local cachesize=$(uci get dhcp.@dnsmasq[0].podkop_cachesize 2>/dev/null)
+ if [ -z "$cachesize" ]; then
+ log "dnsmasq revert: cachesize is unset"
+ else
+ uci set dhcp.@dnsmasq[0].cachesize="$cachesize"
+ fi
+
+ local noresolv=$(uci get dhcp.@dnsmasq[0].podkop_noresolv 2>/dev/null)
+ if [[ "$noresolv" == "unset" ]]; then
+ log "dnsmasq revert: noresolv is unset"
+ uci -q delete dhcp.@dnsmasq[0].noresolv
+ else
+ uci set dhcp.@dnsmasq[0].noresolv="$noresolv"
+ fi
+
+ local server=$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)
+ if [[ "$server" == "127.0.0.42" ]]; then
+ uci -q delete dhcp.@dnsmasq[0].server
+ for server in $(uci get dhcp.@dnsmasq[0].podkop_server 2>/dev/null); do
+ uci add_list dhcp.@dnsmasq[0].server="$server"
+ done
+ uci delete dhcp.@dnsmasq[0].podkop_server
+ fi
+
+ uci delete dhcp.@dnsmasq[0].podkop_cachesize
+ uci delete dhcp.@dnsmasq[0].podkop_noresolv
+
+ uci commit dhcp
+
+ /etc/init.d/dnsmasq restart
+}
+
+process_domains_text() {
+ local text="$1"
+ local name="$2"
+
+ local tmp_file=$(mktemp)
+ echo "$text" > "$tmp_file"
+
+ # First filter out full comment lines and remove comments after domains
+ grep -v "^[[:space:]]*\/\/" "$tmp_file" | sed 's/\/\/.*$//' > "${tmp_file}.filtered"
+
+ sed 's/[, ]\+/\n/g' "${tmp_file}.filtered" | while IFS= read -r domain; do
+ domain=$(echo "$domain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [ -n "$domain" ]; then
+ sing_box_ruleset_domains "$domain" "$name"
+ fi
+ done
+
+ rm -f "$tmp_file" "${tmp_file}.filtered"
+}
+
+process_subnets_text() {
+ local text="$1"
+ local name="$2"
+
+ local tmp_file=$(mktemp)
+ echo "$text" > "$tmp_file"
+
+ # First filter out full comment lines and remove comments after subnets
+ grep -v "^[[:space:]]*\/\/" "$tmp_file" | sed 's/\/\/.*$//' > "${tmp_file}.filtered"
+
+ sed 's/[, ]\+/\n/g' "${tmp_file}.filtered" | while IFS= read -r subnet; do
+ subnet=$(echo "$subnet" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [ -n "$subnet" ]; then
+ if ! echo "$subnet" | grep -q "/"; then
+ subnet="$subnet/32"
+ fi
+ sing_box_ruleset_subnets "$subnet" "$name"
+ fi
+ done
+
+ rm -f "$tmp_file" "${tmp_file}.filtered"
+}
+
+add_cron_job() {
+ ## Future: make a check so that it doesn't recreate many times
+ config_get domain_list_enabled "$section" "domain_list_enabled"
+ config_get subnets_list_enabled "$section" "subnets_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"
+ config_get update_interval "main" "update_interval"
+
+ case "$update_interval" in
+ "1h")
+ cron_job="13 * * * * /etc/init.d/podkop list_update"
+ ;;
+ "3h")
+ cron_job="13 */3 * * * /etc/init.d/podkop list_update"
+ ;;
+ "12h")
+ cron_job="13 */12 * * * /etc/init.d/podkop list_update"
+ ;;
+ "1d")
+ cron_job="13 9 * * * /etc/init.d/podkop list_update"
+ ;;
+ "3d")
+ cron_job="13 9 */3 * * /etc/init.d/podkop list_update"
+ ;;
+ *)
+ log "Invalid update_interval value: $update_interval"
+ return
+ ;;
+ esac
+
+ if [ "$domain_list_enabled" -eq 1 ] || [ "$subnets_list_enabled" -eq 1 ] ||
+ [ "$custom_download_domains_list_enabled" -eq 1 ] || [ "$custom_download_subnets_list_enabled" -eq 1 ] ; then
+ remove_cron_job
+ crontab -l | {
+ cat
+ echo "$cron_job"
+ } | crontab -
+ log "The cron job has been created: $cron_job"
+ fi
+}
+
+remove_cron_job() {
+ (crontab -l | grep -v "/etc/init.d/podkop list_update") | crontab -
+ log "The cron job removed"
+}
+
+list_update() {
+ log "Update remote lists"
+ config_foreach process_remote_ruleset
+ config_foreach process_domains_list_url
+ config_foreach process_subnet_for_section_remote
+}
+
+find_working_resolver() {
+ local resolver_found=""
+ for resolver in $DNS_RESOLVERS; do
+ if nslookup $TEST_DOMAIN $resolver >/dev/null 2>&1; then
+ echo "$resolver"
+ return 0
+ fi
+ done
+ echo "8.8.8.8"
+ return 1
+}
+
+# sing-box funcs
+
+sing_box_uci() {
+ local config="/etc/config/sing-box"
+ if grep -q "option enabled '0'" "$config" ||
+ grep -q "option user 'sing-box'" "$config"; then
+ sed -i \
+ -e "s/option enabled '0'/option enabled '1'/" \
+ -e "s/option user 'sing-box'/option user 'root'/" $config
+ log "Change sing-box UCI config"
+ else
+ log "Sing-box UCI config OK"
+ fi
+}
+
+# Future: for every section. +1 port?
+process_socks5() {
+ config_get_bool socks5 "main" "socks5" "0"
+ if [ "$socks5" -eq 1 ]; then
+ log "Socks5 local enable port 2080"
+ jq '.inbounds += [{
+ "tag": "mixed-in",
+ "type": "mixed",
+ "listen": "0.0.0.0",
+ "listen_port": 2080,
+ "set_system_proxy": false
+ }]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+
+ #local rule_exists=$(jq -r '.route.rules[] | select(.inbound[] == "mixed-in")' $SING_BOX_CONFIG)
+ local rule_exists=$(jq -r '.route.rules // [] | map(select(.inbound // [] | index("mixed-in"))) | length' $SING_BOX_CONFIG)
+
+ if [ -z "$rule_exists" ]; then
+ jq '.route.rules += [{
+ "inbound": ["mixed-in"],
+ "outbound": "main",
+ "action": "route"
+ }]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+ fi
+ fi
+}
+
+sing_box_inbound_proxy() {
+ local listen_port="$1"
+
+ jq -n \
+ --arg listen_port "$listen_port" \
+ '{
+ "log": {
+ "level": "warn"
+ },
+ "inbounds": [
+ {
+ "tag": "tproxy-in",
+ "type": "tproxy",
+ "listen": "::",
+ "listen_port": ($listen_port|tonumber),
+ "tcp_fast_open": true,
+ "udp_fragment": true
+ },
+ {
+ "tag": "dns-in",
+ "type": "direct",
+ "listen": "127.0.0.42",
+ "listen_port": 53
+ }
+ ],
+ "outbounds": [
+ {
+ "tag": "direct-out",
+ "type": "direct"
+ }
+ ]
+ }' > $SING_BOX_CONFIG
+}
+
+sing_box_dns() {
+ local dns_type
+ local dns_server
+ local resolver_tag="resolver"
+
+ config_get dns_type "main" "dns_type" "doh"
+ config_get dns_server "main" "dns_server" "1.1.1.1"
+
+ local server_json
+ local is_ip=$(echo "$dns_server" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' && echo "1" || echo "0")
+
+ if [ "$is_ip" = "0" ]; then
+ log "Finding working DNS resolver"
+ local dns_resolver=$(find_working_resolver)
+ log "Found working resolver: $dns_resolver"
+ fi
+
+ log "Configure DNS in sing-box"
+
+ server_json=$(jq -n \
+ --arg type "$dns_type" \
+ --arg server "$dns_server" \
+ --arg resolver "$resolver_tag" \
+ --arg is_ip "$is_ip" \
+ '{
+ "servers": [
+ {
+ "tag": "dns-server",
+ "address": (
+ if $type == "doh" then
+ "https://" + $server + "/dns-query"
+ elif $type == "dot" then
+ "tls://" + $server
+ else
+ $server
+ end
+ ),
+ "detour": "direct-out"
+ } + (
+ if $is_ip == "0" then
+ {"address_resolver": $resolver}
+ else
+ {}
+ end
+ )
+ ]
+ }')
+
+ if [ "$is_ip" = "0" ]; then
+ server_json=$(echo "$server_json" | jq \
+ --arg resolver "$resolver_tag" \
+ --arg address "$dns_resolver" \
+ '.servers += [{
+ "tag": $resolver,
+ "address": $address
+ }]')
+ fi
+
+ server_json=$(echo "$server_json" | jq '.servers += [{"tag": "fakeip-server", "address": "fakeip"}]')
+
+ jq \
+ --argjson dns_config "$server_json" \
+ --arg fakeip "$FAKEIP" \
+ '.dns = {
+ "strategy": "ipv4_only",
+ "fakeip": {
+ "enabled": true,
+ "inet4_range": $fakeip
+ },
+ "servers": $dns_config.servers
+ }' $SING_BOX_CONFIG > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+}
+
+sing_box_dns_rule_fakeip() {
+ local rewrite_ttl
+ config_get rewrite_ttl "main" "dns_rewrite_ttl" "600"
+
+ log "Configure fakeip route in sing-box and set TTL to $rewrite_ttl seconds"
+
+ jq \
+ --arg ttl "$rewrite_ttl" \
+ '.dns += {
+ "rules": [
+ {
+ "query_type": [
+ "HTTPS"
+ ],
+ "action": "reject"
+ },
+ {
+ "domain_suffix": [
+ "use-application-dns.net"
+ ],
+ "action": "reject"
+ },
+ {
+ "server": "fakeip-server",
+ "domain": "",
+ "rewrite_ttl": ($ttl | tonumber),
+ "rule_set": []
+ }
+ ]
+ }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+}
+
+sing_box_dns_rule_fakeip_section() {
+ local rule_set=$1
+ echo $rule_set
+ log "Adding section to fakeip route rules in sing-box"
+
+ jq \
+ --arg rule_set "$rule_set" \
+ '.dns.rules |= map(
+ if .server == "fakeip-server" then
+ if any(.rule_set[]?; . == $rule_set) then
+ .
+ else
+ .rule_set += [$rule_set]
+ end
+ else
+ .
+ end
+ )' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
+}
+
+sing_box_cache_file() {
+ config_get cache_file "main" "cache_file" "/tmp/cache.db"
+
+ log "Configure sing-box cache.db path"
+
+ jq \
+ --arg cache_file "$cache_file" \
+ '.experimental = {
+ "cache_file": {
+ "enabled": true,
+ "store_fakeip": true,
+ "path": $cache_file
+ }
+ }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+}
+
+sing_box_outdound() {
+ local section="$1"
+
+ config_get mode "$section" "mode"
+ case "$mode" in
+ "vpn")
+ log "VPN mode"
+ log "You are using VPN mode, make sure you have installed all the necessary packages and configured."
+ config_get interface "$section" "interface"
+ sing_box_outbound_interface $section $interface
+ ;;
+ "proxy")
+ log "Proxy mode"
+ config_get proxy_config_type "$section" "proxy_config_type"
+
+ if [ "$proxy_config_type" = "outbound" ]; then
+ config_get outbound_json $section "outbound_json"
+ if [ -n "$outbound_json" ]; then
+ log "Using JSON outbound configuration"
+ sing_box_config_outbound_json "$outbound_json" "$section"
+ else
+ log "Missing outbound JSON configuration"
+ return
+ fi
+ else
+ config_get proxy_string $section "proxy_string"
+
+ # Extract the first non-comment line as the active configuration
+ active_proxy_string=$(echo "$proxy_string" | grep -v "^[[:space:]]*\/\/" | head -n 1)
+
+ if [ -z "$active_proxy_string" ]; then
+ log "No active proxy configuration found"
+ return
+ fi
+
+ if [[ "$active_proxy_string" =~ ^ss:// ]]; then
+ sing_box_config_shadowsocks "$section" "$active_proxy_string"
+ elif [[ "$active_proxy_string" =~ ^vless:// ]]; then
+ sing_box_config_vless "$section" "$active_proxy_string"
+ else
+ log "Unsupported proxy type or missing configuration"
+ return
+ fi
+ fi
+ ;;
+ *)
+ log "Requires *vpn* or *proxy* value"
+ return
+ ;;
+ esac
+}
+
+sing_box_outbound_interface() {
+ local section="$1"
+ local interface="$2"
+
+ jq --arg section "$section" \
+ --arg interface "$interface" \
+ '. |
+ .outbounds |= (
+ map(
+ if .tag == $section then
+ . + {"type": "direct", "bind_interface": $interface}
+ else . end
+ ) +
+ (
+ if (map(select(.tag == $section)) | length) == 0 then
+ [{"tag": $section, "type": "direct", "bind_interface": $interface}]
+ else [] end
+ )
+ )' "$SING_BOX_CONFIG" > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
+
+ if [ $? -eq 0 ]; then
+ log "Config updated successfully"
+ else
+ log "Error: Invalid JSON config generated"
+ return 1
+ fi
+}
+
+sing_box_rule_dns() {
+ log "Configure rule dns in sing-box"
+ jq \
+ '.route += {
+ "rules": [
+ {
+ "inbound": [
+ "dns-in",
+ "tproxy-in"
+ ],
+ "action": "sniff"
+ },
+ {
+ "protocol": "dns",
+ "action": "hijack-dns"
+ }
+ ],
+ "auto_detect_interface": true
+ }' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+}
+
+sing_box_config_check() {
+ if ! sing-box -c $SING_BOX_CONFIG check >/dev/null 2>&1; then
+ log "Sing-box configuration is invalid"
+ exit 1
+ fi
+}
+
+sing_box_config_outbound_json() {
+ local json_config="$1"
+ local section="$2"
+
+ # Create new object with tag first, then merge with the rest of the config
+ local modified_config=$(echo "$json_config" | jq --arg section "$section" \
+ 'del(.tag) | {"tag": $section} + .')
+
+ jq --argjson outbound "$modified_config" \
+ --arg section "$section" \
+ '. |
+ .outbounds |= (
+ map(
+ if .tag == $section then
+ $outbound
+ else . end
+ ) +
+ (
+ if (map(select(.tag == $section)) | length) == 0 then
+ [$outbound]
+ else [] end
+ )
+ )' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+
+ if [ $? -eq 0 ]; then
+ log "Outbound config updated successfully"
+ else
+ log "Error: Invalid JSON config generated"
+ return 1
+ fi
+}
+
+sing_box_config_shadowsocks() {
+ local section="$1"
+ local STRING="$2"
+
+ if echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null | grep -q ":"; then
+ local encrypted_part=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | base64 -d 2>/dev/null )
+ local method=$(echo "$encrypted_part" | cut -d':' -f1)
+ local password=$(echo "$encrypted_part" | cut -d':' -f2-)
+ else
+ local method_and_password=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1)
+ local method=$(echo "$method_and_password" | cut -d':' -f1)
+ local password=$(echo "$method_and_password" | cut -d':' -f2- | sed 's/%3D/=/g')
+ if echo "$method" | base64 -d ; then
+ method=$(echo "$method" | base64 -d)
+ fi
+ fi
+
+ local server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1)
+ local port=$(echo "$STRING" | sed -n 's|.*:\([0-9]\+\).*|\1|p')
+
+ jq \
+ --arg section "$section" \
+ --arg server "$server" \
+ --argjson port "$port" \
+ --arg method "$method" \
+ --arg password "$password" \
+ '. |
+ .outbounds |= (
+ map(
+ if .tag == $section then
+ . + {
+ "type": "shadowsocks",
+ "server": $server,
+ "server_port": ($port | tonumber),
+ "method": $method,
+ "password": $password,
+ "udp_over_tcp": { "enabled": true, "version": 2 }
+ }
+ else . end
+ ) +
+ (
+ if (map(select(.tag == $section)) | length) == 0 then
+ [{
+ "tag": $section,
+ "type": "shadowsocks",
+ "server": $server,
+ "server_port": ($port | tonumber),
+ "method": $method,
+ "password": $password,
+ "udp_over_tcp": { "enabled": true, "version": 2 }
+ }]
+ else [] end
+ )
+ )' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+
+ if [ $? -eq 0 ]; then
+ log "Config updated successfully"
+ else
+ log "Error: Invalid JSON config generated"
+ return 1
+ fi
+}
+
+sing_box_config_vless() {
+ local section="$1"
+ local STRING="$2"
+
+ get_param() {
+ local param="$1"
+ local value=$(echo "$STRING" | sed -n "s/.*[?&]$param=\([^&?#]*\).*/\1/p")
+ value=$(echo "$value" | sed 's/%2F/\//g; s/%2C/,/g; s/%3D/=/g; s/%2B/+/g; s/%20/ /g; s/%3B/;/g' | tr -d '\n' | tr -d '\r')
+ echo "$value"
+ }
+
+ uuid=$(echo "$STRING" | cut -d'/' -f3 | cut -d'@' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
+ server=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
+ port=$(echo "$STRING" | cut -d'@' -f2 | cut -d':' -f2 | cut -d'?' -f1 | cut -d'/' -f1 | cut -d'#' -f1 | tr -d '\n' | tr -d '\r' | sed 's/False//g')
+
+ jq \
+ --arg server "$server" \
+ --argjson port "$port" \
+ --arg uuid "$uuid" \
+ --arg type "$(get_param "type")" \
+ --arg flow "$(get_param "flow")" \
+ --arg sni "$(get_param "sni")" \
+ --arg fp "$(get_param "fp")" \
+ --arg security "$(get_param "security")" \
+ --arg pbk "$(get_param "pbk")" \
+ --arg sid "$(get_param "sid")" \
+ --arg alpn "$(get_param "alpn")" \
+ --arg path "$(get_param "path")" \
+ --arg host "$(get_param "host")" \
+ --arg spx "$(get_param "spx")" \
+ --arg insecure "$(get_param "allowInsecure")" \
+ --arg section "$section" \
+ '. |
+ # Updating an existing outbound by tag or adding a new one
+ .outbounds |= (
+ # If an element with the required tag is found, update it
+ map(
+ if .tag == $section then
+ . + {
+ "type": "vless",
+ "server": $server,
+ "server_port": ($port | tonumber),
+ "uuid": $uuid,
+ "packet_encoding": "",
+ "domain_strategy": "",
+ "flow": $flow
+ }
+ else . end
+ ) +
+ # Add a new outbound if the required tag is not present
+ (
+ if (map(select(.tag == $section)) | length) == 0 then
+ [{
+ "tag": $section,
+ "type": "vless",
+ "server": $server,
+ "server_port": ($port | tonumber),
+ "uuid": $uuid,
+ "packet_encoding": "",
+ "domain_strategy": "",
+ "flow": $flow
+ }]
+ else [] end
+ )
+ ) |
+ # Additional parameters such as transport and tls
+ if $flow != "" then
+ .outbounds |= map(
+ if .tag == $section then
+ .flow = $flow
+ else . end
+ )
+ else . end |
+ if $type == "ws" then
+ .outbounds |= map(
+ if .tag == $section then
+ .transport = {
+ "type": "ws",
+ "path": $path
+ } |
+ if $host != "" then
+ .transport.headers = { "Host": $host }
+ else . end
+ else . end
+ )
+ elif $type == "grpc" then
+ .outbounds |= map(
+ if .tag == $section then
+ .transport = { "type": "grpc" }
+ else . end
+ )
+ else . end |
+ if $security == "reality" or $security == "tls" then
+ .outbounds |= map(
+ if .tag == $section then
+ .tls = {
+ "enabled": true,
+ "server_name": $sni,
+ "utls": {
+ "enabled": true,
+ "fingerprint": $fp
+ },
+ "insecure": ($insecure == "1")
+ } |
+ if $alpn != "" then
+ .tls.alpn = ($alpn | split(","))
+ else . end |
+ if $security == "reality" then
+ .tls.reality = {
+ "enabled": true,
+ "public_key": $pbk,
+ "short_id": $sid
+ }
+ else . end
+ else . end
+ )
+ else . end' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+
+
+ if [ $? -eq 0 ]; then
+ log "Config created successfully"
+ else
+ log "Error: Invalid JSON config generated"
+ return 1
+ fi
+}
+
+# Process. Sing-box rules
+
+sing_box_ruleset_domains() {
+ log "Configure ruleset domains in sing-box"
+
+ local domain=$1
+ local tag=$2
+
+ # Check if there is a route.rule_set for the specified tag
+ local tag_exists=$(jq -r --arg tag "$tag" '
+ .route.rule_set[]? | select(.tag == $tag) | .tag
+ ' /etc/sing-box/config.json)
+
+ # If the tag exists, add the domain
+ if [[ -n "$tag_exists" ]]; then
+ jq \
+ --arg tag "$tag" \
+ --arg domain "$domain" \
+ '
+ .route.rule_set[] |=
+ if .tag == $tag then
+ .rules[0].domain_suffix += [$domain]
+ else
+ .
+ end
+ ' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
+
+ log "$domain added to the list for tag $tag"
+ else
+ # If tag does not exist, add a new set of rules
+ jq \
+ --arg tag "$tag" \
+ --arg domain "$domain" \
+ '
+ .route.rule_set += [
+ {
+ "tag": $tag,
+ "type": "inline",
+ "rules": [
+ {
+ "domain_suffix": [$domain]
+ }
+ ]
+ }
+ ]' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
+
+ log "$domain added as a new rule set for tag $tag"
+ fi
+}
+
+sing_box_ruleset_subnets() {
+ log "Configure ruleset domains in sing-box"
+
+ local subnet=$1
+ local tag=$2
+
+ # nft
+ nft add element inet PodkopTable podkop_subnets { $subnet }
+
+ # Check if there is a route.rule_set for the specified tag
+ local tag_exists=$(jq -r --arg tag "$tag" '
+ .route.rule_set[]? | select(.tag == $tag) | .tag
+ ' /etc/sing-box/config.json)
+
+ # If tag exists, add the domain
+ if [[ -n "$tag_exists" ]]; then
+ jq \
+ --arg tag "$tag" \
+ --arg subnet "$subnet" \
+ '
+ .route.rule_set[] |=
+ if .tag == $tag then
+ .rules[0].ip_cidr += [$subnet]
+ else
+ .
+ end
+ ' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
+
+ log "$subnet added to the list for tag $tag"
+ else
+ # If tag does not exist, add a new set of rules
+ jq \
+ --arg tag "$tag" \
+ --arg subnet "$subnet" \
+ '
+ .route.rule_set += [
+ {
+ "tag": $tag,
+ "type": "inline",
+ "rules": [
+ {
+ "ip_cidr": [$subnet]
+ }
+ ]
+ }
+ ]' /etc/sing-box/config.json > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json /etc/sing-box/config.json
+
+ log "$subnet added as a new rule set for tag $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"
+ if [ "$custom_domains_list_type" = "dynamic" ]; then
+ # Handle list domains from custom_domains
+ config_list_foreach "$section" custom_domains "sing_box_ruleset_domains" "$section"
+ elif [ "$custom_domains_list_type" = "text" ]; then
+ # Handle domains from text
+ config_get custom_domains_text "$section" "custom_domains_text"
+ process_domains_text "$custom_domains_text" "$section"
+ fi
+ fi
+}
+
+sing_box_ruleset_remote() {
+ local tag=$1
+ local type=$2
+ local update_interval=$3
+
+ 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" \
+ '
+ .route.rule_set += [
+ {
+ "tag": $tag,
+ "type": $type,
+ "format": "binary",
+ "url": $url,
+ "update_interval": $update_interval
+ }
+ ]' "$SING_BOX_CONFIG" > /tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$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
+ ;;
+ "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 "br-lan" ip daddr @podkop_discord_subnets udp dport { 50000-65535 } meta mark set 0x105 counter
+ ;;
+ *)
+ return
+ ;;
+ esac
+
+ local filename=$(basename "$URL")
+ wget -O "/tmp/podkop/$filename" "$URL"
+
+ 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"
+ local outbound="$2"
+
+ # Check if there is an outbound rule for "tproxy-in"
+ local rule_exists=$(jq -r '.route.rules[] | select(.outbound == "'"$outbound"'" and .inbound == ["tproxy-in"])' "$SING_BOX_CONFIG")
+
+ if [[ -n "$rule_exists" ]]; then
+ # If a rule for tproxy-in exists, add a new rule_set to the existing rule
+ jq \
+ --arg rule_set "$rule_set" \
+ --arg outbound "$outbound" \
+ '(.route.rules[] | select(.outbound == $outbound and .inbound == ["tproxy-in"]) .rule_set) += [$rule_set]' \
+ "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
+ else
+ # If there is no rule for tproxy-in, create a new one with rule_set
+ jq \
+ --arg rule_set "$rule_set" \
+ --arg outbound "$outbound" \
+ '.route.rules += [{
+ "inbound": ["tproxy-in"],
+ "rule_set": [$rule_set],
+ "outbound": $outbound,
+ "action": "route"
+ }]' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
+ fi
+}
+
+sing_box_quic_reject() {
+ local quic_rule_exists=$(jq -e '.route.rules[] | select(.protocol == "quic" and .action == "reject")' "$SING_BOX_CONFIG")
+
+ if [[ -z "$quic_rule_exists" ]]; then
+ jq '
+ .route.rules |= (
+ reduce .[] as $rule ([];
+ if $rule.protocol == "dns" and $rule.action == "hijack-dns" then
+ . + [$rule, {"protocol": "quic", "action": "reject"}]
+ else
+ . + [$rule]
+ end
+ )
+ )' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
+
+ log "QUIC reject rule added successfully"
+ fi
+}
+
+process_remote_ruleset() {
+ 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 "sing_box_ruleset_remote" "remote" "1d"
+ config_list_foreach "$section" domain_list "list_subnets_download" "$section" "$domain_list"
+ fi
+}
+
+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" ] || [ "$custom_download_domains_list_enabled" = "1" ] ||
+ [ "$custom_download_subnets_list_enabled" = "1" ]; then
+ sing_box_rules "$section" "$section"
+ fi
+
+ if [ "$custom_domains_list_type" != "disabled" ] || [ "$custom_local_domains_list_enabled" = "1" ] ||
+ [ "$custom_download_domains_list_enabled" = "1" ]; then
+ sing_box_dns_rule_fakeip_section "$section" "$section"
+ fi
+
+ config_get domain_list_enabled "$section" "domain_list_enabled"
+ config_get domain_list "$section" "domain_list"
+ if [ "$domain_list_enabled" -eq 1 ]; then
+ config_list_foreach $section domain_list sing_box_rules $section
+ config_list_foreach $section domain_list sing_box_dns_rule_fakeip_section domain_list
+ fi
+}
+
+list_custom_local_domains_create() {
+ local section="$2"
+ local local_file="$1"
+ local filename=$(basename "$local_file" | cut -d. -f1)
+
+ while IFS= read -r domain; do
+ domain=$(echo "$domain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
+ if [ -n "$domain" ] && echo "$domain" | grep -E -q '^([a-zA-Z0-9][-a-zA-Z0-9]*\.)+[a-zA-Z]{2,}$'; then
+ log "Added $domain from local file"
+ sing_box_ruleset_domains "$domain" "$section"
+ else
+ log "Invalid domain skipped: $domain"
+ fi
+ done <"$local_file"
+}
+
+process_domains_list_local() {
+ local section="$1"
+
+ config_get custom_local_domains_list_enabled "$section" "custom_local_domains_list_enabled"
+ if [ "$custom_local_domains_list_enabled" -eq 1 ]; then
+ log "Adding a custom domains list from file in $section"
+ config_list_foreach "$section" "custom_local_domains" list_custom_local_domains_create "$section"
+ fi
+}
+
+list_custom_url_domains_create() {
+ local section="$2"
+ local URL="$1"
+ local filename=$(basename "$URL")
+
+ wget -q -O "/tmp/podkop/${filename}" "$URL"
+
+ while IFS= read -r domain; do
+ log "From local file: $domain"
+ sing_box_ruleset_domains $domain $section
+ done <"/tmp/podkop/$filename"
+}
+
+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"
+ if [ "$custom_subnets_list_enabled" = "dynamic" ]; then
+ # Handle list domains from custom_domains
+ config_list_foreach "$section" custom_subnets "sing_box_ruleset_subnets" "$section"
+ elif [ "$custom_subnets_list_enabled" = "text" ]; then
+ # Handle domains from text
+ config_get custom_subnets_text "$section" "custom_subnets_text"
+ process_subnets_text "$custom_subnets_text" "$section"
+ fi
+ fi
+}
+
+list_custom_url_subnets_create() {
+ local section="$2"
+ local URL="$1"
+ local filename=$(basename "$URL")
+
+ wget -q -O "/tmp/podkop/${filename}" "$URL"
+
+ while IFS= read -r subnet; do
+ log "From local file: $subnet"
+ sing_box_ruleset_subnets $subnet $section
+ done <"/tmp/podkop/$filename"
+}
+
+process_subnet_for_section_remote() {
+ 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"
+ fi
+}
+
+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 sing_box_rules_source_ip_cidr $all_traffic_ip $section
+ 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)
+
+
+ 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 >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+ else
+ jq \
+ --arg source_ip_cidr "$source_ip_cidr" \
+ --arg outbound "$outbound" \
+ '.route.rules = [
+ {
+ "inbound": ["tproxy-in"],
+ "source_ip_cidr": [$source_ip_cidr],
+ "outbound": $outbound,
+ "action": "route"
+ }
+ ] + .route.rules' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
+ fi
+}
+
+## nftables
+list_all_traffic_from_ip() {
+ local ip="$1"
+ if ! nft list chain inet PodkopTable mangle | grep -q "ip saddr $ip"; then
+ nft add set inet PodkopTable localv4 { type ipv4_addr\; flags interval\; }
+ nft add element inet PodkopTable localv4 { \
+ 0.0.0.0/8, \
+ 10.0.0.0/8, \
+ 127.0.0.0/8, \
+ 169.254.0.0/16, \
+ 172.16.0.0/12, \
+ 192.0.0.0/24, \
+ 192.0.2.0/24, \
+ 192.88.99.0/24, \
+ 192.168.0.0/16, \
+ 198.51.100.0/24, \
+ 203.0.113.0/24, \
+ 224.0.0.0/4, \
+ 240.0.0.0-255.255.255.255 }
+ nft insert rule inet PodkopTable mangle iifname "br-lan" ip saddr $ip meta l4proto { tcp, udp } meta mark set 0x105 counter
+ nft insert rule inet PodkopTable mangle ip saddr $ip ip daddr @localv4 return
+ fi
+}
+
+# Diagnotics
+check_proxy() {
+ if ! command -v sing-box >/dev/null 2>&1; then
+ nolog "sing-box is not installed"
+ return 1
+ fi
+
+ if [ ! -f $SING_BOX_CONFIG ]; then
+ nolog "Configuration file not found"
+ return 1
+ fi
+
+ nolog "Checking sing-box configuration..."
+
+ if ! sing-box -c $SING_BOX_CONFIG check >/dev/null; then
+ nolog "Invalid configuration"
+ return 1
+ fi
+
+ jq '
+ walk(
+ if type == "object" then
+ with_entries(
+ if .key == "uuid" then
+ .value = "MASKED"
+ elif .key == "server" then
+ .value = "MASKED"
+ elif .key == "server_name" then
+ .value = "MASKED"
+ elif .key == "password" then
+ .value = "MASKED"
+ elif .key == "public_key" then
+ .value = "MASKED"
+ elif .key == "short_id" then
+ .value = "MASKED"
+ elif .key == "fingerprint" then
+ .value = "MASKED"
+ elif .key == "server_port" then
+ .value = "MASKED"
+ else . end
+ )
+ else . end
+ )' $SING_BOX_CONFIG
+
+ nolog "Checking proxy connection..."
+
+
+ for attempt in `seq 1 5`; do
+ response=$(sing-box tools fetch ifconfig.me -D /etc/sing-box 2>/dev/null)
+ if echo "$response" | grep -q "^/dev/null 2>&1; then
+ nolog "nft is not installed"
+ return 1
+ fi
+
+ nolog "Checking PodkopTable rules..."
+
+ # Check if table exists
+ if ! nft list table inet PodkopTable >/dev/null 2>&1; then
+ nolog "PodkopTable not found"
+ return 1
+ fi
+
+ # Get all sets
+ nolog "\nSets configuration:"
+
+ nft list table inet PodkopTable
+
+ nolog "\nNFT check completed"
+}
+
+check_github() {
+ nolog "Checking GitHub connectivity..."
+
+ if ! curl -m 3 github.com; then
+ nolog "Error: Cannot connect to GitHub"
+ return 1
+ fi
+ nolog "GitHub is accessible"
+
+ nolog "Checking lists availability:"
+ for url in "$DOMAINS_RU_INSIDE" "$DOMAINS_RU_OUTSIDE" "$DOMAINS_UA" "$DOMAINS_YOUTUBE" \
+ "$SUBNETS_TWITTER" "$SUBNETS_META" "$SUBNETS_DISCORD"; do
+ local list_name=$(basename "$url")
+ wget -q -O /dev/null "$url"
+ if [ $? -eq 0 ]; then
+ nolog "- $list_name: available"
+ else
+ nolog "- $list_name: not available"
+ fi
+ done
+}
+
+check_dnsmasq() {
+ nolog "Checking dnsmasq configuration..."
+
+ local config=$(uci show dhcp.@dnsmasq[0])
+ if [ -z "$config" ]; then
+ nolog "No dnsmasq configuration found"
+ return 1
+ fi
+
+ echo "$config" | while IFS='=' read -r key value; do
+ nolog "$key = $value"
+ done
+}
+
+check_sing_box_connections() {
+ nolog "Checking sing-box connections..."
+
+ if ! command -v netstat >/dev/null 2>&1; then
+ nolog "netstat is not installed"
+ return 1
+ fi
+
+ local connections=$(netstat -tuanp | grep sing-box)
+ if [ -z "$connections" ]; then
+ nolog "No active sing-box connections found"
+ return 1
+ fi
+
+ echo "$connections" | while read -r line; do
+ nolog "$line"
+ done
+}
+
+check_sing_box_logs() {
+ nolog "Showing sing-box logs from system journal..."
+
+ local logs=$(logread -e sing-box | tail -n 50)
+ if [ -z "$logs" ]; then
+ nolog "No sing-box logs found"
+ return 1
+ fi
+
+ echo "$logs"
+}
+
+check_logs() {
+ nolog "Showing podkop logs from system journal..."
+
+ if command -v logread >/dev/null 2>&1; then
+ logread -e podkop | tail -n 50
+ else
+ nolog "Error: logread command not found"
+ return 1
+ fi
+}
+
+show_sing_box_config() {
+ nolog "Current sing-box configuration:"
+
+ if [ ! -f "$SING_BOX_CONFIG" ]; then
+ nolog "Configuration file not found"
+ return 1
+ fi
+
+ jq '
+ walk(
+ if type == "object" then
+ with_entries(
+ if .key == "uuid" then
+ .value = "MASKED"
+ elif .key == "server" then
+ .value = "MASKED"
+ elif .key == "server_name" then
+ .value = "MASKED"
+ elif .key == "password" then
+ .value = "MASKED"
+ elif .key == "public_key" then
+ .value = "MASKED"
+ elif .key == "short_id" then
+ .value = "MASKED"
+ elif .key == "fingerprint" then
+ .value = "MASKED"
+ elif .key == "server_port" then
+ .value = "MASKED"
+ else . end
+ )
+ else . end
+ )' "$SING_BOX_CONFIG"
+}
+
+show_config() {
+ nolog "Current podkop configuration:"
+
+ if [ ! -f /etc/config/podkop ]; then
+ nolog "Configuration file not found"
+ return 1
+ fi
+
+ tmp_config=$(mktemp)
+
+ cat /etc/config/podkop | sed \
+ -e 's/\(option proxy_string\).*/\1 '\''MASKED'\''/g' \
+ -e 's/\(option outbound_json\).*/\1 '\''MASKED'\''/g' \
+ -e 's/\(option second_proxy_string\).*/\1 '\''MASKED'\''/g' \
+ -e 's/\(option second_outbound_json\).*/\1 '\''MASKED'\''/g' \
+ -e 's/\(vless:\/\/[^@]*@\)/vless:\/\/MASKED@/g' \
+ -e 's/\(ss:\/\/[^@]*@\)/ss:\/\/MASKED@/g' \
+ -e 's/\(pbk=[^&]*\)/pbk=MASKED/g' \
+ -e 's/\(sid=[^&]*\)/sid=MASKED/g' \
+ > "$tmp_config"
+
+ cat "$tmp_config"
+ rm -f "$tmp_config"
+}
+
+show_version() {
+ local version=$(opkg info podkop | grep -m 1 "Version:" | cut -d' ' -f2)
+ echo "$version"
+}
+
+show_luci_version() {
+ local version=$(opkg info luci-app-podkop | grep -m 1 "Version:" | cut -d' ' -f2)
+ echo "$version"
+}
+
+show_sing_box_version() {
+ local version=$(opkg info sing-box | grep -m 1 "Version:" | cut -d' ' -f2)
+ echo "$version"
+}
+
+show_system_info() {
+ echo "=== OpenWrt Version ==="
+ grep OPENWRT_RELEASE /etc/os-release | cut -d'"' -f2
+ echo
+ echo "=== Device Model ==="
+ cat /tmp/sysinfo/model
+}
+
+get_sing_box_status() {
+ local running=0
+ local enabled=0
+ local status=""
+ local version=""
+ local dns_configured=0
+
+ # Check if service is enabled
+ if [ -x /etc/rc.d/S99sing-box ]; then
+ enabled=1
+ fi
+
+ # Check if service is running
+ if pgrep -f "sing-box" >/dev/null; then
+ running=1
+ version=$(sing-box version | head -n 1 | awk '{print $3}')
+ fi
+
+ # Check DNS configuration
+ local dns_server=$(uci get dhcp.@dnsmasq[0].server 2>/dev/null)
+ if [ "$dns_server" = "127.0.0.42" ]; then
+ dns_configured=1
+ fi
+
+ # Format status message
+ if [ $running -eq 1 ]; then
+ if [ $enabled -eq 1 ]; then
+ status="running & enabled"
+ else
+ status="running but disabled"
+ fi
+ else
+ if [ $enabled -eq 1 ]; then
+ status="stopped but enabled"
+ else
+ status="stopped & disabled"
+ fi
+ fi
+
+ echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\",\"dns_configured\":$dns_configured}"
+}
+
+get_status() {
+ local running=0
+ local enabled=0
+ local status=""
+
+ # Check if service is enabled
+ if [ -x /etc/rc.d/S99podkop ]; then
+ enabled=1
+ fi
+
+ # Check if service is running
+ if pgrep -f "sing-box" >/dev/null; then
+ running=1
+ fi
+
+ # Format status message
+ if [ $running -eq 1 ]; then
+ if [ $enabled -eq 1 ]; then
+ status="running & enabled"
+ else
+ status="running but disabled"
+ fi
+ else
+ if [ $enabled -eq 1 ]; then
+ status="stopped but enabled"
+ else
+ status="stopped & disabled"
+ fi
+ fi
+
+ echo "{\"running\":$running,\"enabled\":$enabled,\"status\":\"$status\"}"
+}
+
+sing_box_add_secure_dns_probe_domain() {
+ local domain="httpbin.org"
+ local override_address="numbersapi.com"
+
+ if [ -z "$override_address" ]; then
+ log "Error: Could not get br-lan IP address"
+ return 1
+ fi
+
+ log "Adding DNS probe domain ${domain} to fakeip-server configuration"
+
+ jq \
+ --arg domain "$domain" \
+ --arg override "$override_address" \
+ '.dns.rules |= map(
+ if .server == "fakeip-server" then
+ . + {
+ "domain": $domain
+ }
+ else
+ .
+ end
+ ) |
+ .route.rules |= . + [
+ {
+ "domain": $domain,
+ "action": "route-options",
+ "override_address": $override
+ }
+ ]' "$SING_BOX_CONFIG" >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json "$SING_BOX_CONFIG"
+
+ log "DNS probe domain ${domain} configured with override to ${override_address}"
+}
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ main)
+ main
+ ;;
+ list_update)
+ list_update
+ ;;
+ check_proxy)
+ check_proxy
+ ;;
+ check_nft)
+ check_nft
+ ;;
+ check_github)
+ check_github
+ ;;
+ check_logs)
+ check_logs
+ ;;
+ check_sing_box_connections)
+ check_sing_box_connections
+ ;;
+ check_sing_box_logs)
+ check_sing_box_logs
+ ;;
+ check_dnsmasq)
+ check_dnsmasq
+ ;;
+ show_config)
+ show_config
+ ;;
+ show_version)
+ show_version
+ ;;
+ show_sing_box_config)
+ show_sing_box_config
+ ;;
+ show_luci_version)
+ show_luci_version
+ ;;
+ show_sing_box_version)
+ show_sing_box_version
+ ;;
+ show_system_info)
+ show_system_info
+ ;;
+ get_status)
+ get_status
+ ;;
+ get_sing_box_status)
+ get_sing_box_status
+ ;;
+ *)
+ echo "Usage: $0 {start|stop|restart|reload|enable|disable|main|list_update|check_proxy|check_nft|check_github|check_logs|check_sing_box_connections|check_sing_box_logs|check_dnsmasq|show_config|show_version|show_sing_box_config|show_luci_version|show_sing_box_version|show_system_info|get_status|get_sing_box_status}"
+ exit 1
+ ;;
+esac