2165 lines
66 KiB
Bash
Executable File
2165 lines
66 KiB
Bash
Executable File
#!/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="fakeip.tech-domain.club"
|
|
INTERFACES_LIST=""
|
|
SRC_INTERFACE=""
|
|
|
|
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_create_bypass_ruleset
|
|
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 sing_box_rule_preset
|
|
config_foreach process_domains_list_local
|
|
config_foreach process_subnet_for_section
|
|
config_foreach process_remote_ruleset_srs
|
|
config_foreach process_all_traffic_for_section
|
|
config_foreach add_cron_job
|
|
|
|
config_foreach prepare_custom_ruleset
|
|
list_update &
|
|
echo $! > /var/run/podkop_list_update.pid
|
|
|
|
# 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 start
|
|
/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"
|
|
|
|
if [ -f /var/run/podkop_list_update.pid ]; then
|
|
pid=$(cat /var/run/podkop_list_update.pid)
|
|
if kill -0 "$pid"; then
|
|
kill "$pid"
|
|
log "Stopped list_update"
|
|
fi
|
|
rm -f /var/run/podkop_list_update.pid
|
|
fi
|
|
|
|
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
|
|
|
|
# corntab init.d
|
|
(crontab -l | grep -v "/etc/init.d/podkop list_update") | crontab -
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
process_interfaces() {
|
|
local iface="$1"
|
|
INTERFACES_LIST="$INTERFACES_LIST $iface"
|
|
iface_flag=1
|
|
}
|
|
|
|
nft_interfaces() {
|
|
local table=PodkopTable
|
|
iface_flag=0
|
|
|
|
config_list_foreach "main" "iface" "process_interfaces"
|
|
if [ "$iface_flag" -eq 0 ]; then
|
|
SRC_INTERFACE="br-lan"
|
|
elif [ $(echo "$INTERFACES_LIST" | wc -w) -eq 1 ]; then
|
|
SRC_INTERFACE=$INTERFACES_LIST
|
|
else
|
|
local set_name="interfaces"
|
|
if ! nft list set inet $table $set_name &>/dev/null; then
|
|
nft add set inet $table $set_name { type ifname\; flags interval\; }
|
|
fi
|
|
|
|
for interface in $INTERFACES_LIST; do
|
|
if ! nft list element inet $table $set_name { $interface } &>/dev/null; then
|
|
nft add element inet $table $set_name { $interface }
|
|
fi
|
|
done
|
|
|
|
SRC_INTERFACE=@$set_name
|
|
fi
|
|
}
|
|
|
|
create_nft_table() {
|
|
local table="PodkopTable"
|
|
|
|
nft add table inet $table
|
|
|
|
nft_interfaces
|
|
|
|
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 "$SRC_INTERFACE" ip daddr @podkop_subnets meta l4proto tcp meta mark set 0x105 counter
|
|
nft add rule inet $table mangle iifname "$SRC_INTERFACE" ip daddr @podkop_subnets meta l4proto udp meta mark set 0x105 counter
|
|
nft add rule inet $table mangle iifname "$SRC_INTERFACE" ip daddr "$FAKEIP" meta l4proto tcp meta mark set 0x105 counter
|
|
nft add rule inet $table mangle iifname "$SRC_INTERFACE" 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 * * * * /usr/bin/podkop list_update"
|
|
;;
|
|
"3h")
|
|
cron_job="13 */3 * * * /usr/bin/podkop list_update"
|
|
;;
|
|
"12h")
|
|
cron_job="13 */12 * * * /usr/bin/podkop list_update"
|
|
;;
|
|
"1d")
|
|
cron_job="13 9 * * * /usr/bin/podkop list_update"
|
|
;;
|
|
"3d")
|
|
cron_job="13 9 */3 * * /usr/bin/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 "/usr/bin/podkop list_update") | crontab -
|
|
log "The cron job removed"
|
|
}
|
|
|
|
prepare_custom_ruleset() {
|
|
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_download_domains_list_enabled" -eq 1 ] || [ "$custom_download_subnets_list_enabled" -eq 1 ]; then
|
|
local file="/tmp/podkop/$section-custom-domains-subnets.json"
|
|
local tag="custom-$section"
|
|
rm -f $file
|
|
|
|
jq -n '
|
|
{
|
|
"version": 3,
|
|
"rules": []
|
|
}' > $file
|
|
|
|
jq --arg tag "$tag" \
|
|
--arg file "$file" \
|
|
'.route.rule_set += [{
|
|
"tag": $tag,
|
|
"type": "local",
|
|
"format": "source",
|
|
"path": $file
|
|
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
|
|
|
|
sing_box_rules $tag $section
|
|
sing_box_dns_rule_fakeip_section $tag $tag
|
|
|
|
log "Added 'test' rule_set to sing-box config"
|
|
fi
|
|
}
|
|
|
|
list_update() {
|
|
log "Update remote lists"
|
|
|
|
local i
|
|
|
|
for i in $(seq 1 60); do
|
|
if nslookup -timeout=1 openwrt.org >/dev/null 2>&1; then
|
|
log "DNS is available"
|
|
break
|
|
fi
|
|
log "DNS is unavailable [$i/60]"
|
|
sleep 3
|
|
done
|
|
|
|
if [ "$i" -eq 60 ]; then
|
|
log "Error: DNS check failed after 10 attempts"
|
|
return 1
|
|
fi
|
|
|
|
for i in $(seq 1 60); do
|
|
if curl -s -m 3 https://github.com >/dev/null; then
|
|
log "GitHub is available"
|
|
break
|
|
fi
|
|
log "GitHub is unavailable [$i/60]"
|
|
sleep 3
|
|
done
|
|
|
|
if [ "$i" -eq 60 ]; then
|
|
log "Error: Cannot connect to GitHub after 10 attempts"
|
|
return 1
|
|
fi
|
|
|
|
config_foreach process_remote_ruleset_subnet
|
|
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 -timeout=2 $TEST_DOMAIN $resolver >/dev/null 2>&1; then
|
|
echo "$resolver"
|
|
return 0
|
|
fi
|
|
done
|
|
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"
|
|
fi
|
|
|
|
if grep -q '#\s*list ifaces' "$config"; then
|
|
sed -i '/ifaces/s/#//g' $config
|
|
log "Uncommented list ifaces"
|
|
fi
|
|
}
|
|
|
|
add_socks5_for_section() {
|
|
local section="$1"
|
|
local port="$2"
|
|
local tag="$section-mixed-in"
|
|
|
|
log "Adding Socks5 for $section on port $port"
|
|
|
|
jq \
|
|
--arg tag "$tag" \
|
|
--arg port "$port" \
|
|
--arg section "$section" \
|
|
'.inbounds += [{
|
|
"tag": $tag,
|
|
"type": "mixed",
|
|
"listen": "0.0.0.0",
|
|
"listen_port": ($port|tonumber),
|
|
"set_system_proxy": false
|
|
}] |
|
|
.route.rules += [{
|
|
"inbound": [$tag],
|
|
"outbound": $section,
|
|
"action": "route"
|
|
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
|
|
}
|
|
|
|
process_socks5() {
|
|
config_get_bool main_socks5 "main" "socks5" "0"
|
|
if [ "$main_socks5" -eq 1 ]; then
|
|
add_socks5_for_section "main" "2080"
|
|
fi
|
|
|
|
local port=2081
|
|
for section in $(uci show podkop | awk -F'[.=]' '/=extra/ {print $2}'); do
|
|
config_get_bool section_socks5 "$section" "socks5" "0"
|
|
if [ "$section_socks5" -eq 1 ]; then
|
|
add_socks5_for_section "$section" "$port"
|
|
port=$((port + 1))
|
|
fi
|
|
done
|
|
}
|
|
|
|
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)
|
|
if [ -z "$dns_resolver" ]; then
|
|
log "No working resolver found, using default DNS server"
|
|
dns_resolver="1.1.1.1"
|
|
else
|
|
log "Found working resolver: $dns_resolver"
|
|
fi
|
|
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_create_bypass_ruleset() {
|
|
log "Creating bypass ruleset for direct access"
|
|
|
|
jq '
|
|
.route.rule_set += [{
|
|
"tag": "bypass",
|
|
"type": "inline",
|
|
"rules": [
|
|
{
|
|
"domain_suffix": [
|
|
"ip.tech-domain.club"
|
|
]
|
|
}
|
|
]
|
|
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
|
|
|
|
# Add a rule to route bypass domains to direct-out outbound
|
|
jq '
|
|
.route.rules += [{
|
|
"inbound": ["tproxy-in"],
|
|
"rule_set": ["bypass"],
|
|
"outbound": "main",
|
|
"action": "route"
|
|
}]' $SING_BOX_CONFIG >/tmp/sing-box-config-tmp.json && mv /tmp/sing-box-config-tmp.json $SING_BOX_CONFIG
|
|
|
|
# Make sure the bypass ruleset is in the fakeip DNS rule
|
|
jq '
|
|
.dns.rules = (.dns.rules | map(
|
|
if .server == "fakeip-server" then
|
|
.rule_set += ["bypass"]
|
|
else
|
|
.
|
|
end
|
|
))' $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"
|
|
|
|
if [ -z "$interface" ]; then
|
|
log "VPN interface is not set. Exit"
|
|
exit 1
|
|
fi
|
|
|
|
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 "Proxy string is not set. Exit"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$active_proxy_string" =~ ^ss:// ]]; then
|
|
config_get ss_uot $section "ss_uot"
|
|
sing_box_config_shadowsocks "$section" "$active_proxy_string" "$ss_uot"
|
|
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"
|
|
ss_uot="${3:-0}"
|
|
|
|
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" \
|
|
--argjson ss_uot "$ss_uot" \
|
|
'. |
|
|
.outbounds |= (
|
|
map(
|
|
if .tag == $section then
|
|
. + {
|
|
"type": "shadowsocks",
|
|
"server": $server,
|
|
"server_port": ($port | tonumber),
|
|
"method": $method,
|
|
"password": $password
|
|
} + (if $ss_uot == 1 then { "udp_over_tcp": { "enabled": true, "version": 2 } } else {} end)
|
|
else . end
|
|
) +
|
|
(
|
|
if (map(select(.tag == $section)) | length) == 0 then
|
|
[{
|
|
"tag": $section,
|
|
"type": "shadowsocks",
|
|
"server": $server,
|
|
"server_port": ($port | tonumber),
|
|
"method": $method,
|
|
"password": $password
|
|
} + (if $ss_uot == 1 then { "udp_over_tcp": { "enabled": true, "version": 2 } } 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 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
|
|
}
|
|
|
|
sing_box_ruleset_domains_json() {
|
|
local domain="$1"
|
|
local section="$2"
|
|
|
|
local file="/tmp/podkop/$section-custom-domains-subnets.json"
|
|
|
|
jq --arg domain "$domain" '
|
|
.rules[0].domain_suffix += if .rules[0].domain_suffix | index($domain) then [] else [$domain] end
|
|
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
|
|
|
log "$domain added to $section-custom-domains-subnets.json"
|
|
}
|
|
|
|
sing_box_ruleset_subnets_json() {
|
|
local subnet="$1"
|
|
local section="$2"
|
|
|
|
local file="/tmp/podkop/$section-custom-domains-subnets.json"
|
|
|
|
jq --arg subnet "$subnet" '
|
|
.rules[0].ip_cidr += if .rules[0].ip_cidr | index($subnet) then [] else [$subnet] end
|
|
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
|
|
|
log "$subnet added to $section-custom-domains-subnets.json"
|
|
}
|
|
|
|
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 "$SRC_INTERFACE" 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_srs() {
|
|
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"
|
|
fi
|
|
}
|
|
|
|
process_remote_ruleset_subnet() {
|
|
config_get_bool domain_list_enabled "$section" "domain_list_enabled" "0"
|
|
if [ "$domain_list_enabled" -eq 1 ]; then
|
|
log "Adding a srs list for $section"
|
|
config_list_foreach "$section" domain_list "list_subnets_download" "$section" "$domain_list"
|
|
fi
|
|
}
|
|
|
|
sing_box_rule_preset() {
|
|
config_get custom_domains_list_type "$section" "custom_domains_list_type"
|
|
config_get custom_subnets_list_enabled "$section" "custom_subnets_list_enabled"
|
|
config_get custom_local_domains_list_enabled "$section" "custom_local_domains_list_enabled"
|
|
# config_get custom_download_domains_list_enabled "$section" "custom_download_domains_list_enabled"
|
|
# config_get custom_download_subnets_list_enabled "$section" "custom_download_subnets_list_enabled"
|
|
|
|
if [ "$custom_domains_list_type" != "disabled" ] || [ "$custom_subnets_list_enabled" != "disabled" ] ||
|
|
[ "$custom_local_domains_list_enabled" = "1" ]; then
|
|
sing_box_rules "$section" "$section"
|
|
fi
|
|
|
|
if [ "$custom_domains_list_type" != "disabled" ] || [ "$custom_local_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 downloaded file: $domain"
|
|
sing_box_ruleset_domains_json $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_json $subnet $section
|
|
nft add element inet PodkopTable podkop_subnets { $subnet }
|
|
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"
|
|
local table="PodkopTable"
|
|
|
|
if ! nft list chain inet $table mangle | grep -q "ip saddr $ip"; then
|
|
nft add set inet $table localv4 { type ipv4_addr\; flags interval\; }
|
|
nft add element inet $table 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 $table mangle iifname "$SRC_INTERFACE" ip saddr $ip meta l4proto { tcp, udp } meta mark set 0x105 counter
|
|
nft insert rule inet $table 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_fakeip() {
|
|
nolog "Checking fakeip functionality..."
|
|
|
|
if ! command -v nslookup >/dev/null 2>&1; then
|
|
nolog "nslookup is not installed"
|
|
return 1
|
|
fi
|
|
|
|
local test_domain="$TEST_DOMAIN"
|
|
|
|
nolog "Testing DNS resolution with default DNS server"
|
|
echo "=== Testing with default DNS server ==="
|
|
nslookup -timeout=2 $test_domain
|
|
echo ""
|
|
|
|
nolog "Finding a working DNS resolver..."
|
|
local working_resolver=$(find_working_resolver)
|
|
if [ -z "$working_resolver" ]; then
|
|
nolog "No working resolver found, skipping resolver check"
|
|
else
|
|
nolog "Using resolver: $working_resolver"
|
|
|
|
nolog "Testing DNS resolution with working resolver ($working_resolver)"
|
|
echo "=== Testing with working resolver ($working_resolver) ==="
|
|
nslookup -timeout=2 $test_domain $working_resolver
|
|
echo ""
|
|
fi
|
|
|
|
# Main FakeIP check
|
|
nolog "Testing DNS resolution for $test_domain using 127.0.0.42"
|
|
echo "=== Testing with FakeIP DNS (127.0.0.42) ==="
|
|
local result=$(nslookup -timeout=2 $test_domain 127.0.0.42 2>&1)
|
|
echo "$result"
|
|
|
|
if echo "$result" | grep -q "198.18"; then
|
|
nolog "✅ FakeIP is working correctly! Domain resolved to FakeIP range (198.18.x.x)"
|
|
return 0
|
|
else
|
|
nolog "❌ FakeIP test failed. Domain did not resolve to FakeIP range"
|
|
nolog "Checking if sing-box is running..."
|
|
|
|
if ! pgrep -f "sing-box" >/dev/null; then
|
|
nolog "sing-box is not running"
|
|
else
|
|
nolog "sing-box is running, but FakeIP might not be configured correctly"
|
|
nolog "Checking DNS configuration in sing-box..."
|
|
|
|
if [ -f "$SING_BOX_CONFIG" ]; then
|
|
local fakeip_enabled=$(jq -r '.dns.fakeip.enabled' "$SING_BOX_CONFIG")
|
|
local fakeip_range=$(jq -r '.dns.fakeip.inet4_range' "$SING_BOX_CONFIG")
|
|
|
|
nolog "FakeIP enabled: $fakeip_enabled"
|
|
nolog "FakeIP range: $fakeip_range"
|
|
|
|
local dns_rules=$(jq -r '.dns.rules[] | select(.server == "fakeip-server") | .domain' "$SING_BOX_CONFIG")
|
|
nolog "FakeIP domain: $dns_rules"
|
|
else
|
|
nolog "sing-box config file not found"
|
|
fi
|
|
fi
|
|
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
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\"}"
|
|
}
|
|
|
|
check_dns_available() {
|
|
local dns_type=$(uci get podkop.main.dns_type 2>/dev/null)
|
|
local dns_server=$(uci get podkop.main.dns_server 2>/dev/null)
|
|
local is_available=0
|
|
local status="unavailable"
|
|
|
|
if [ "$dns_type" = "doh" ]; then
|
|
# Different DoH providers use different endpoints and formats
|
|
local result=""
|
|
|
|
# Try common DoH endpoints and check for valid responses
|
|
# First try /dns-query endpoint (Cloudflare, AdGuard DNS, etc.)
|
|
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/dns-query?name=itdog.info&type=A")
|
|
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
|
|
is_available=1
|
|
status="available"
|
|
else
|
|
# If that fails, try /resolve endpoint (Google DNS)
|
|
result=$(curl --connect-timeout 5 -s -H "accept: application/dns-json" "https://$dns_server/resolve?name=itdog.info&type=A")
|
|
if [ $? -eq 0 ] && echo "$result" | grep -q "data"; then
|
|
is_available=1
|
|
status="available"
|
|
fi
|
|
fi
|
|
elif [ "$dns_type" = "dot" ]; then
|
|
if nc $dns_server 853 </dev/null >/dev/null 2>&1; then
|
|
is_available=1
|
|
status="available"
|
|
fi
|
|
elif [ "$dns_type" = "udp" ]; then
|
|
if nslookup -timeout=2 itdog.info $dns_server >/dev/null 2>&1; then
|
|
is_available=1
|
|
status="available"
|
|
fi
|
|
fi
|
|
|
|
echo "{\"dns_type\":\"$dns_type\",\"dns_server\":\"$dns_server\",\"is_available\":$is_available,\"status\":\"$status\"}"
|
|
}
|
|
|
|
sing_box_add_secure_dns_probe_domain() {
|
|
local domain="$TEST_DOMAIN"
|
|
local override_port=8443
|
|
|
|
log "Adding DNS probe domain ${domain} to fakeip-server configuration"
|
|
|
|
jq \
|
|
--arg domain "$domain" \
|
|
--argjson override_port "$override_port" \
|
|
'.dns.rules |= map(
|
|
if .server == "fakeip-server" then
|
|
. + {
|
|
"domain": $domain
|
|
}
|
|
else
|
|
.
|
|
end
|
|
) |
|
|
.route.rules |= . + [
|
|
{
|
|
"domain": $domain,
|
|
"action": "route-options",
|
|
"override_port": $override_port
|
|
}
|
|
]' "$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 port ${override_port}"
|
|
}
|
|
|
|
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_fakeip)
|
|
check_fakeip
|
|
;;
|
|
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
|
|
;;
|
|
check_dns_available)
|
|
check_dns_available
|
|
;;
|
|
*)
|
|
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_fakeip|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|check_dns_available}"
|
|
exit 1
|
|
;;
|
|
esac |