1608 lines
49 KiB
Bash
Executable File
1608 lines
49 KiB
Bash
Executable File
#!/bin/sh /etc/rc.common
|
|
|
|
START=99
|
|
USE_PROCD=1
|
|
|
|
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"
|
|
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"
|
|
|
|
[ ! -L /usr/sbin/podkop ] && ln -s /etc/init.d/podkop /usr/sbin/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"
|
|
CACHE_FILE_PATH="/tmp/cache.db"
|
|
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"
|
|
|
|
start_service() {
|
|
log "Start podkop"
|
|
|
|
sing_box_version=$(sing-box version | head -n 1 | awk '{print $3}')
|
|
required_version="1.11.1"
|
|
|
|
if [ "$(echo -e "$sing_box_version\n$required_version" | sort -V | head -n 1)" != "$required_version" ]; then
|
|
echo "The version of sing-box ($sing_box_version) is lower than the minimum version. Update sing-box: opkg update && opkg remove sing-box && opkg install sing-box"
|
|
exit 1
|
|
fi
|
|
|
|
if opkg list-installed | grep -qE "iptables|kmod-iptab"; then
|
|
printf "\033[31;1mFound incompatible iptables packages. If you're using FriendlyWrt: https://t.me/itdogchat/44512/181082\033[0m\n"
|
|
fi
|
|
|
|
if ! ip addr | grep -q "br-lan"; then
|
|
log "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 stdout 1
|
|
procd_set_param stderr 1
|
|
procd_close_instance
|
|
}
|
|
|
|
stop_service() {
|
|
log "Stopping the podkop"
|
|
remove_cron_job
|
|
dnsmasq_rm
|
|
|
|
rm -rf /tmp/podkop/*
|
|
|
|
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
|
|
|
|
}
|
|
|
|
restart_service() {
|
|
stop
|
|
start
|
|
}
|
|
|
|
reload_service() {
|
|
stop
|
|
start
|
|
}
|
|
|
|
service_triggers() {
|
|
log "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 5
|
|
|
|
config_foreach wget_github
|
|
|
|
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_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"
|
|
|
|
if [ -n "$proxy_string" ] || [ -n "$interface" ]; then
|
|
dnsmasq_add
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
dnsmasq_add() {
|
|
## Future: Check config and skip restart
|
|
log "Configure dnsmasq for sing-box"
|
|
uci set dhcp.@dnsmasq[0].noresolv="1"
|
|
uci set dhcp.@dnsmasq[0].filter_aaaa="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 add_list dhcp.@dnsmasq[0].server='/use-application-dns.net/'
|
|
uci commit dhcp
|
|
|
|
/etc/init.d/dnsmasq restart
|
|
}
|
|
|
|
dnsmasq_rm() {
|
|
log "Removing configuration for dnsmasq"
|
|
uci set dhcp.@dnsmasq[0].noresolv="0"
|
|
uci set dhcp.@dnsmasq[0].filter_aaaa="0"
|
|
uci set dhcp.@dnsmasq[0].cachesize="1000"
|
|
uci -q delete dhcp.@dnsmasq[0].server
|
|
uci add_list dhcp.@dnsmasq[0].server="8.8.8.8"
|
|
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"
|
|
|
|
sed 's/[, ]\+/\n/g' "$tmp_file" | 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"
|
|
}
|
|
|
|
process_subnets_text() {
|
|
local text="$1"
|
|
local name="$2"
|
|
|
|
local tmp_file=$(mktemp)
|
|
echo "$text" > "$tmp_file"
|
|
|
|
sed 's/[, ]\+/\n/g' "$tmp_file" | 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"
|
|
}
|
|
|
|
wget_github() {
|
|
local count_nslookup=0
|
|
local count_curl=0
|
|
|
|
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"
|
|
|
|
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
|
|
|
|
while true; do
|
|
if ! nslookup google.com >/dev/null 2>&1; then
|
|
log "DNS not working. Retrying... [$count_nslookup sec]"
|
|
count_nslookup=$((count_nslookup + 1))
|
|
else
|
|
return
|
|
fi
|
|
|
|
if [ $count_nslookup -lt 30 ]; then
|
|
sleep_interval=1
|
|
elif [ $count_nslookup -ge 30 ] && [ $count_nslookup -lt 60 ]; then
|
|
sleep_interval=5
|
|
elif [ $count_nslookup -ge 60 ] && [ $count_nslookup -lt 90 ]; then
|
|
sleep_interval=10
|
|
else
|
|
sleep_interval=30
|
|
fi
|
|
|
|
sleep $sleep_interval
|
|
done
|
|
|
|
while true; do
|
|
if ! curl -m 3 github.com; then
|
|
log "GitHub is not available. Check the internet availability [$count_curl sec]"
|
|
count_curl=$((count_curl + 1))
|
|
else
|
|
return
|
|
fi
|
|
|
|
if [ $count_curl -lt 30 ]; then
|
|
sleep_interval=1
|
|
elif [ $count_curl -ge 30 ] && [ $count_curl -lt 60 ]; then
|
|
sleep_interval=5
|
|
elif [ $count_curl -ge 60 ] && [ $count_curl -lt 90 ]; then
|
|
sleep_interval=10
|
|
else
|
|
sleep_interval=30
|
|
fi
|
|
|
|
sleep $sleep_interval
|
|
done
|
|
fi
|
|
}
|
|
|
|
|
|
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
|
|
}
|
|
|
|
|
|
# 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() {
|
|
log "Configure DNS in sing-box"
|
|
jq \
|
|
--arg FAKEIP "$FAKEIP" \
|
|
'.dns = {
|
|
"strategy": "ipv4_only",
|
|
"fakeip": {
|
|
"enabled": true,
|
|
"inet4_range": $FAKEIP
|
|
},
|
|
"servers": [
|
|
{
|
|
"tag": "cloudflare-doh-server",
|
|
"address": "https://1.1.1.1/dns-query",
|
|
"detour": "direct-out"
|
|
},
|
|
{
|
|
"tag": "fakeip-server",
|
|
"address": "fakeip"
|
|
}
|
|
]
|
|
}' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
|
|
}
|
|
|
|
sing_box_dns_rule_fakeip() {
|
|
log "Configure fakeip route in sing-box"
|
|
jq \
|
|
'.dns += {
|
|
"rules": [
|
|
{
|
|
"query_type": [
|
|
"HTTPS"
|
|
],
|
|
"action": "reject"
|
|
},
|
|
{
|
|
"server": "fakeip-server",
|
|
"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() {
|
|
log "Configure cache.db in sing-box"
|
|
jq \
|
|
--arg CACHE_FILE_PATH "$CACHE_FILE_PATH" \
|
|
'.experimental = {
|
|
"cache_file": {
|
|
"enabled": true,
|
|
"store_fakeip": true,
|
|
"path": $CACHE_FILE_PATH
|
|
}
|
|
}' $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"
|
|
else
|
|
log "Missing outbound JSON configuration"
|
|
return
|
|
fi
|
|
else
|
|
config_get proxy_string $section "proxy_string"
|
|
if [[ "$proxy_string" =~ ^ss:// ]]; then
|
|
sing_box_config_shadowsocks "$section" "$proxy_string"
|
|
elif [[ "$proxy_string" =~ ^vless:// ]]; then
|
|
sing_box_config_vless "$section" "$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 listen_port="$2"
|
|
|
|
cat > /tmp/base_config.json << EOF
|
|
{
|
|
"log": {
|
|
"level": "warn"
|
|
},
|
|
"inbounds": [
|
|
{
|
|
"type": "tproxy",
|
|
"listen": "::",
|
|
"listen_port": $listen_port,
|
|
"tcp_fast_open": true,
|
|
"udp_fragment": true
|
|
},
|
|
{
|
|
"tag": "dns-in",
|
|
"type": "direct",
|
|
"listen": "127.0.0.42",
|
|
"listen_port": 53
|
|
}
|
|
],
|
|
"outbounds": [],
|
|
"route": {
|
|
"auto_detect_interface": true
|
|
}
|
|
}
|
|
EOF
|
|
|
|
jq --argjson outbound "$json_config" '.outbounds += [$outbound]' /tmp/base_config.json > $SING_BOX_CONFIG
|
|
rm -f /tmp/base_config.json
|
|
}
|
|
|
|
|
|
|
|
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' | 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 -q -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"
|
|
|
|
if [ "$custom_domains_list_type" != "disabled" ] || [ "$custom_subnets_list_enabled" != "disabled" ]; then
|
|
sing_box_rules "$section" "$section"
|
|
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
|
|
log "From local file: $domain"
|
|
sing_box_ruleset_domains $domain $section
|
|
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 "$section" "$all_traffic_ip"
|
|
fi
|
|
}
|
|
|
|
sing_box_rules_source_ip_cidr() {
|
|
log "Configure source_ip_cidr rule in sing-box"
|
|
local outbound="$2"
|
|
local source_ip_cidr="$1"
|
|
|
|
local current_source_ip_cidr=$(jq -r ".route.rules[] | select(.outbound == \"$outbound\" and .source_ip_cidr) | .rule_set" $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) | .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
|
|
}
|
|
] + .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 "^<html\|403 Forbidden"; then
|
|
continue
|
|
fi
|
|
if [[ $response =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
ip=$(echo "$response" | sed -n 's/^[0-9]\+\.[0-9]\+\.[0-9]\+\.\([0-9]\+\)$/X.X.X.\1/p')
|
|
nolog "$ip - should match proxy IP"
|
|
return 0
|
|
elif echo "$response" | grep -q "^[0-9a-fA-F:]*::[0-9a-fA-F:]*$\|^[0-9a-fA-F:]\+$"; then
|
|
ip=$(echo "$response" | sed 's/\([0-9a-fA-F]\+:[0-9a-fA-F]\+:[0-9a-fA-F]\+\):.*/\1:XXXX:XXXX:XXXX/')
|
|
nolog "$ip - should match proxy IP"
|
|
return 0
|
|
fi
|
|
if [ $attempt -eq 5 ]; then
|
|
nolog "Failed to get valid IP address after 5 attempts"
|
|
if [ -z "$response" ]; then
|
|
nolog "Error: Empty response"
|
|
else
|
|
nolog "Error response: $response"
|
|
fi
|
|
return 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
check_nft() {
|
|
if ! command -v nft >/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"
|
|
} |