Files
openwrt_helloworld/luci-app-ssr-plus/root/usr/bin/ssr-rules
Xiaokailnol 3a983ab130
All checks were successful
openwrt_helloworld / Update openwrt_helloworld (openwrt-25.12) (push) Successful in 27s
🎄 Sync 2026-01-31 03:15:58
2026-01-31 03:15:58 +00:00

1986 lines
61 KiB
Bash
Executable File

#!/bin/sh
#
# Copyright (C) 2017 openwrt-ssr
# Copyright (C) 2017 yushi studio <ywb94@qq.com>
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#
. $IPKG_INSTROOT/etc/init.d/shadowsocksr
# Detect firewall version and set appropriate tools
detect_firewall() {
check_run_environment
case "$USE_TABLES" in
nftables)
USE_NFT=1
NFT="nft"
echolog "ssr-rules: Using nftables"
;;
iptables)
USE_NFT=0
IPT="iptables -t nat" # alias of iptables TCP
ipt="iptables -t mangle" # alias of iptables UDP
echolog "ssr-rules: Using iptables"
;;
*)
echolog "ERROR: No supported firewall backend"
return 1
;;
esac
FWI=$(uci get firewall.shadowsocksr.path 2>/dev/null) # firewall include file
}
# Initialize firewall detection
detect_firewall
TAG="_SS_SPEC_RULE_" # comment tag
# Initialize all global switch variables (for both NFT and IPT)
# These variables will be set in getopts parameter parsing
ENABLE_AUTO_UPDATE=0
STOP_AUTO_UPDATE=0
FORCE_UPDATE=0
CHECK_STATUS=0
RESTORE_RULES=0
FLUSH_RULES=0
CLEANUP_PERSISTENCE=0
if [ "$USE_NFT" = "1" ]; then
# NFTables persistence directory
NFTABLES_RULES_DIR="/usr/share/nftables.d/ruleset-post"
NFTABLES_RULES_FILE="$NFTABLES_RULES_DIR/99-shadowsocksr.nft"
# Auto-update configuration
AUTO_UPDATE_INTERVAL=300 # auto-update check interval (seconds), 0 means disable
fi
# Modified usage function
usage() {
cat <<-EOF
Usage: ssr-rules [options]
Valid options are:
-s <server_ip> ip address of shadowsocksr remote server
-l <local_port> port number of shadowsocksr local server
-S <server_ip> ip address of shadowsocksr remote UDP server
-L <local_port> port number of shadowsocksr local UDP server
-i <ip_list_file> a file content is bypassed ip list
-a <lan_ips> lan ip of access control, need a prefix to
define access control mode
-b <wan_ips> wan ip of will be bypassed
-w <wan_ips> wan ip of will be forwarded
-B <bp_lan_ips> lan ip of will be bypassed proxy
-p <fp_lan_ips> lan ip of will be global proxy
-G <gm_lan_ips> lan ip of will be game mode proxy
-D <proxy_ports> proxy ports
-F shunt mode
-N shunt server IP
-M shunt proxy mode
-m <Interface> Interface name
-I <ip_list_file> a file content is bypassed shunt ip list
-e <extra_options> extra options for iptables
-o apply the rules to the OUTPUT chain
-O apply the global rules to the OUTPUT chain
-u enable udprelay mode, TPROXY is required
-U enable udprelay mode, using different IP
and ports for TCP and UDP
-f flush the rules
-g gfwlist mode
-r router mode
-c oversea mode
-z all mode
# New persistence management options (use different letters to avoid conflicts)
-A enable auto-update daemon
-K stop auto-update daemon
-P force update persistence
-C check rules status
-R restore rules from persistence file
-X cleanup persistence files on stop
-h show this help message and exit
EOF
exit $1
}
loger() {
# 1.alert 2.crit 3.err 4.warn 5.notice 6.info 7.debug
logger -st ssr-rules[$$] -p$1 $2
}
# IP list normalization function (for comparison)
normalize_ip_list() {
echo "$1" | tr ' ' '\n' | sort | tr '\n' ' ' | sed 's/ $//'
}
# Check if IP list has changed
check_ip_list_changed() {
local current_list="$1"
local last_list="$2"
local list_name="$3"
local current_norm=$(normalize_ip_list "$current_list")
local last_norm=$(normalize_ip_list "$last_list")
if [ "$current_norm" != "$last_norm" ]; then
loger 6 "$list_name changed: '$last_norm' -> '$current_norm'"
return 1 # changed
else
loger 6 "$list_name unchanged: '$current_norm'"
return 0 # unchanged
fi
}
# Cleanup persistence and runtime module files
cleanup_persistence_files() {
if [ "$USE_NFT" != "1" ]; then
return 0
fi
# Remove persistence rule file
if [ -f "$NFTABLES_RULES_FILE" ]; then
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
loger 5 "Removed persistence file: $NFTABLES_RULES_FILE"
fi
# Remove run mode file
if [ -f "/tmp/.ssr_run_mode" ]; then
rm -f "/tmp/.ssr_run_mode" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.ssr_run_mode"
fi
# Remove TPROXY file
if [ -f "/tmp/.last_tproxy" ]; then
rm -f "/tmp/.last_tproxy" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.last_tproxy"
fi
# Remove PROXY_PORTS file
if [ -f "/tmp/.last_proxy_ports" ]; then
rm -f "/tmp/.last_proxy_ports" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.last_proxy_ports"
fi
# Remove WAN_BP_IP file
if [ -f "/tmp/.last_wan_bp_ip" ]; then
rm -f "/tmp/.last_wan_bp_ip" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.last_wan_bp_ip"
fi
# Remove LAN_AC_IP file
if [ -f "/tmp/.last_lan_ac_ip" ]; then
rm -f "/tmp/.last_lan_ac_ip" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.last_lan_ac_ip"
fi
# Remove LAN_BP_IP file
if [ -f "/tmp/.last_lan_bp_ip" ]; then
rm -f "/tmp/.last_lan_bp_ip" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.last_lan_bp_ip"
fi
# Remove WAN_FW_IP file
if [ -f "/tmp/.last_wan_fw_ip" ]; then
rm -f "/tmp/.last_wan_fw_ip" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.last_wan_fw_ip"
fi
# Remove LAN_FP_IP file
if [ -f "/tmp/.last_lan_fp_ip" ]; then
rm -f "/tmp/.last_lan_fp_ip" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.last_lan_fp_ip"
fi
# Remove LAN_GM_IP file
if [ -f "/tmp/.last_lan_gm_ip" ]; then
rm -f "/tmp/.last_lan_gm_ip" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.last_lan_gm_ip"
fi
# Remove xhttp file state and hash files
if [ -f "/tmp/.last_xhttp_file" ]; then
rm -f "/tmp/.last_xhttp_file" 2>/dev/null
loger 5 "Removed xhttp file state: /tmp/.last_xhttp_file"
fi
if [ -f "/tmp/.last_xhttp_hash" ]; then
rm -f "/tmp/.last_xhttp_hash" 2>/dev/null
loger 5 "Removed xhttp hash file: /tmp/.last_xhttp_hash"
fi
loger 5 "Persistence cleanup completed"
return 0
}
flush_r() {
if [ "$USE_NFT" = "1" ]; then
flush_nftables
else
flush_iptables_legacy
fi
return 0
}
flush_nftables() {
# Delete inet ss_spec table
if $NFT list table inet ss_spec >/dev/null 2>&1; then
# Delete all chains
local CHAINS=$($NFT list table inet ss_spec | awk '/chain [a-zA-Z0-9_-]+/ {print $2}' | sort -u)
for chain in $CHAINS; do
$NFT flush chain inet ss_spec $chain 2>/dev/null
$NFT delete chain inet ss_spec $chain 2>/dev/null
done
# Delete all sets
local SETS=$($NFT list table inet ss_spec | awk '/set [a-zA-Z0-9_-]+/ {print $2}' | sort -u)
for setname in $SETS; do
$NFT flush set inet ss_spec $setname 2>/dev/null
$NFT delete set inet ss_spec $setname 2>/dev/null
done
# Delete entire table
$NFT delete table inet ss_spec 2>/dev/null
fi
# Delete ip ss_spec_mangle table (if exists)
if $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then
# Delete all chains
local CHAINS=$($NFT list table ip ss_spec_mangle | awk '/chain [a-zA-Z0-9_-]+/ {print $2}' | sort -u)
for chain in $CHAINS; do
$NFT flush chain ip ss_spec_mangle $chain 2>/dev/null
$NFT delete chain ip ss_spec_mangle $chain 2>/dev/null
done
# Delete all sets
local SETS=$($NFT list table ip ss_spec_mangle | awk '/set [a-zA-Z0-9_-]+/ {print $2}' | sort -u)
for setname in $SETS; do
$NFT flush set ip ss_spec_mangle $setname 2>/dev/null
$NFT delete set ip ss_spec_mangle $setname 2>/dev/null
done
# Delete entire table
$NFT delete table ip ss_spec_mangle 2>/dev/null
fi
# Delete policy routing mark rules
ip rule del fwmark 0x01/0x01 table 100 2>/dev/null
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
# Optional: force delete all ss_spec related sets (even if table was accidentally deleted)
for setname in ss_spec_lan_ac ss_spec_wan_ac ssr_gen_router \
china fplan bplan gmlan oversea whitelist blacklist netflix gfwlist music; do
$NFT delete set inet ss_spec $setname 2>/dev/null
$NFT delete set ip ss_spec_mangle $setname 2>/dev/null
done
# Reset firewall include file
[ -n "$FWI" ] && echo '#!/bin/sh' >"$FWI"
# Cleanup persistence and runtime module files
if [ "$CLEANUP_PERSISTENCE" = "1" ]; then
cleanup_persistence_files
fi
loger 6 "Memory rules flushed successfully"
return 0
}
flush_iptables_legacy() {
flush_iptables() {
local ipt="iptables -t $1"
local DAT=$(iptables-save -t $1)
eval $(echo "$DAT" | grep "$TAG" | sed -e 's/^-A/$ipt -D/' -e 's/$/;/')
for chain in $(echo "$DAT" | awk '/^:SS_SPEC/{print $1}'); do
$ipt -F ${chain:1} 2>/dev/null && $ipt -X ${chain:1}
done
}
flush_iptables nat
flush_iptables mangle
ip rule del fwmark 0x01/0x01 table 100 2>/dev/null
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
for setname in ss_spec_lan_ac ss_spec_wan_ac ssr_gen_router \
china fplan bplan gmlan oversea whitelist blacklist netflix gfwlist music; do
ipset -X $setname 2>/dev/null
done
[ -n "$FWI" ] && echo '#!/bin/sh' >$FWI
return 0
}
ipset_r() {
if [ "$USE_NFT" = "1" ]; then
ipset_nft
else
ipset_iptables
fi
return $?
}
ipset_nft() {
# Create nftables table and sets
if ! $NFT list table inet ss_spec >/dev/null 2>&1; then
$NFT add table inet ss_spec 2>/dev/null
fi
# Create necessary collections
for setname in china gmlan fplan bplan whitelist blacklist netflix music; do
if ! $NFT list set inet ss_spec $setname >/dev/null 2>&1; then
$NFT add set inet ss_spec $setname '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
else
$NFT flush set inet ss_spec $setname 2>/dev/null
fi
done
# Bulk import china ip list safely (avoid huge single element limitation)
if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then
$NFT add element inet ss_spec china "{ $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') }" 2>/dev/null
fi
# Bulk import xhttp ip list into nft whitelist (server + shunt)
if [ -f "${xhttp_ip:=/etc/ssrplus/xhttp_address.txt}" ]; then
$NFT add element inet ss_spec whitelist "{ $(tr '\n' ',' < "${xhttp_ip}" | sed 's/,$//') }" 2>/dev/null
fi
# Add IP addresses to sets
for ip in $LAN_GM_IP; do
[ -n "$ip" ] && $NFT add element inet ss_spec gmlan "{ $ip }" 2>/dev/null
done
for ip in $LAN_FP_IP; do
[ -n "$ip" ] && $NFT add element inet ss_spec fplan "{ $ip }" 2>/dev/null
done
for ip in $LAN_BP_IP; do
[ -n "$ip" ] && $NFT add element inet ss_spec bplan "{ $ip }" 2>/dev/null
done
for ip in $WAN_BP_IP; do
[ -n "$ip" ] && $NFT add element inet ss_spec whitelist "{ $ip }" 2>/dev/null
done
for ip in $WAN_FW_IP; do
[ -n "$ip" ] && $NFT add element inet ss_spec blacklist "{ $ip }" 2>/dev/null
done
# Create main chains for WAN access control
for chain in ss_spec_wan_fw ss_spec_wan_ac; do
if ! $NFT list chain inet ss_spec $chain >/dev/null 2>&1; then
$NFT add chain inet ss_spec $chain
fi
$NFT flush chain inet ss_spec $chain
done
# Add basic rules
# BASIC RULES (exceptions first) — TCP
$NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp tcp dport 53 ip daddr 127.0.0.0/8 return
[ -n "$server" ] && $NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp tcp dport != 53 ip daddr "$server" return
# Access control: blacklist -> whitelist -> fplan/bplan — TCP
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @blacklist jump ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @whitelist return
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @fplan jump ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @bplan return
# Music unlocking support
if $NFT list set inet ss_spec music >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac meta l4proto tcp ip daddr @music return
fi
# Shunt/Netflix rules
if [ -f "$SHUNT_LIST" ]; then
for ip in $(cat "$SHUNT_LIST" 2>/dev/null); do
[ -n "$ip" ] && $NFT add element inet ss_spec netflix "{ $ip }" 2>/dev/null
done
fi
# Set up mode-specific rules
case "$RUNMODE" in
router)
if ! $NFT list set inet ss_spec ss_spec_wan_ac >/dev/null 2>&1; then
$NFT add set inet ss_spec ss_spec_wan_ac '{ type ipv4_addr; flags interval; auto-merge; }'
else
$NFT flush set inet ss_spec ss_spec_wan_ac 2>/dev/null
fi
# Add special IP ranges to WAN AC set
for ip in $(gen_spec_iplist); do
[ -n "$ip" ] && $NFT add element inet ss_spec ss_spec_wan_ac "{ $ip }" 2>/dev/null
done
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @ss_spec_wan_ac return
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china return
if $NFT list chain inet ss_spec ss_spec_wan_ac >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_ac jump ss_spec_wan_fw
fi
;;
gfw)
if ! $NFT list set inet ss_spec gfwlist >/dev/null 2>&1; then
$NFT add set inet ss_spec gfwlist '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
fi
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china return
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @gfwlist jump ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw
;;
oversea)
if ! $NFT list set inet ss_spec oversea >/dev/null 2>&1; then
$NFT add set inet ss_spec oversea '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
fi
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @oversea jump ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan jump ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china jump ss_spec_wan_fw
;;
all)
if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac jump ss_spec_wan_fw
fi
;;
esac
return $?
}
ipset_iptables() {
[ -f "$IGNORE_LIST" ] && /usr/share/shadowsocksr/chinaipset.sh "$IGNORE_LIST"
$IPT -N SS_SPEC_WAN_AC 2>/dev/null
$IPT -F SS_SPEC_WAN_AC
$IPT -I SS_SPEC_WAN_AC -p tcp --dport 53 -d 127.0.0.0/8 -j RETURN
$IPT -I SS_SPEC_WAN_AC -p tcp ! --dport 53 -d "$server" -j RETURN
ipset -N gmlan hash:net 2>/dev/null
for ip in $LAN_GM_IP; do ipset -! add gmlan "$ip"; done
case "$RUNMODE" in
router)
ipset -! -R <<-EOF || return 1
create ss_spec_wan_ac hash:net
$(gen_spec_iplist | sed -e "s/^/add ss_spec_wan_ac /")
EOF
$IPT -A SS_SPEC_WAN_AC -m set --match-set ss_spec_wan_ac dst -j RETURN
$IPT -A SS_SPEC_WAN_AC -m set --match-set china dst -j RETURN
$IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -m set ! --match-set china dst -j SS_SPEC_WAN_FW
$IPT -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW
;;
gfw)
ipset -N gfwlist hash:net 2>/dev/null
$IPT -A SS_SPEC_WAN_AC -m set --match-set china dst -j RETURN
$IPT -A SS_SPEC_WAN_AC -m set --match-set gfwlist dst -j SS_SPEC_WAN_FW
$IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -m set ! --match-set china dst -j SS_SPEC_WAN_FW
;;
oversea)
ipset -N oversea hash:net 2>/dev/null
$IPT -I SS_SPEC_WAN_AC -m set --match-set oversea dst -j SS_SPEC_WAN_FW
$IPT -A SS_SPEC_WAN_AC -m set --match-set gmlan src -j SS_SPEC_WAN_FW
$IPT -A SS_SPEC_WAN_AC -m set --match-set china dst -j SS_SPEC_WAN_FW
;;
all)
$IPT -A SS_SPEC_WAN_AC -j SS_SPEC_WAN_FW
;;
esac
ipset -N fplan hash:net 2>/dev/null
for ip in $LAN_FP_IP; do ipset -! add fplan "$ip"; done
$IPT -I SS_SPEC_WAN_AC -m set --match-set fplan src -j SS_SPEC_WAN_FW
ipset -N bplan hash:net 2>/dev/null
for ip in $LAN_BP_IP; do ipset -! add bplan "$ip"; done
$IPT -I SS_SPEC_WAN_AC -m set --match-set bplan src -j RETURN
ipset -N whitelist hash:net 2>/dev/null
if [ -f "${xhttp_ip:=/etc/ssrplus/xhttp_address.txt}" ]; then
while IFS= read -r ip; do
[ -n "$ip" ] && ipset add whitelist "$ip" -exist
done < "$xhttp_ip"
fi
ipset -N blacklist hash:net 2>/dev/null
$IPT -I SS_SPEC_WAN_AC -m set --match-set blacklist dst -j SS_SPEC_WAN_FW
$IPT -I SS_SPEC_WAN_AC -m set --match-set whitelist dst -j RETURN
if [ $(ipset list music -name -quiet | grep music) ]; then
$IPT -I SS_SPEC_WAN_AC -m set --match-set music dst -j RETURN 2>/dev/null
fi
for ip in $WAN_BP_IP; do ipset -! add whitelist "$ip"; done
for ip in $WAN_FW_IP; do ipset -! add blacklist "$ip"; done
if [ "$SHUNT_PORT" != "0" ]; then
ipset -N netflix hash:net 2>/dev/null
for ip in $(cat "${SHUNT_LIST:=/dev/null}" 2>/dev/null); do ipset -! add netflix "$ip"; done
case "$SHUNT_PORT" in
0) ;;
1)
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$local_port"
;;
*)
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$SHUNT_PORT"
if [ "$SHUNT_PROXY" = "1" ]; then
$IPT -I SS_SPEC_WAN_AC -p tcp -d "$SHUNT_IP" -j REDIRECT --to-ports "$local_port"
else
ipset -! add whitelist "$SHUNT_IP"
fi
;;
esac
fi
return $?
}
fw_rule() {
if [ "$USE_NFT" = "1" ]; then
fw_rule_nft
else
fw_rule_iptables
fi
return $?
}
fw_rule_nft() {
# set up routing table for tproxy
if ! ip rule show | grep -Eq "fwmark 0x0*1.*lookup 100"; then
ip rule add fwmark 0x01/0x01 table 100 2>/dev/null
fi
if ! ip route show table 100 | grep -q "^local.*dev lo"; then
ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null
fi
# redirect/translation: when PROXY_PORTS present, redirect those tcp ports to local_port
if [ -n "$PROXY_PORTS" ]; then
PORTS_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
if [ -n "$PORTS_ARGS" ]; then
TCP_EXT_ARGS="meta l4proto tcp tcp dport { $PORTS_ARGS }"
TCP_RULE="meta l4proto tcp tcp dport { $PORTS_ARGS } counter redirect to :$local_port"
fi
else
TCP_EXT_ARGS="meta l4proto tcp"
# default: redirect everything except ssh(22)
TCP_RULE="meta l4proto tcp tcp dport != 22 counter redirect to :$local_port"
fi
# add TCP rule to fw chain if not exists (use -F exact match)
if ! $NFT list chain inet ss_spec ss_spec_wan_fw 2>/dev/null | grep -F -- "$TCP_RULE" >/dev/null 2>&1; then
if ! $NFT add rule inet ss_spec ss_spec_wan_fw $TCP_RULE 2>/dev/null; then
loger 3 "Can't redirect TCP, please check nftables."
return 1
fi
fi
if [ "$SHUNT_PORT" != "0" ] && [ -f "$SHUNT_LIST" ]; then
case "$SHUNT_PORT" in
1)
$NFT add rule inet ss_spec ss_spec_wan_ac $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$local_port
;;
*)
$NFT add rule inet ss_spec ss_spec_wan_ac $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$SHUNT_PORT
if [ "$SHUNT_PROXY" = "1" ]; then
$NFT add rule inet ss_spec ss_spec_wan_ac $TCP_EXT_ARGS ip daddr $SHUNT_IP counter redirect to :$local_port
else
[ -n "$SHUNT_IP" ] && $NFT add element inet ss_spec whitelist "{ $SHUNT_IP }" 2>/dev/null
fi
;;
esac
fi
return $?
}
fw_rule_iptables() {
# set up routing table for tproxy
if ! ip rule show | grep -Eq "fwmark 0x0*1.*lookup 100"; then
ip rule add fwmark 0x01/0x01 table 100 2>/dev/null
fi
if ! ip route show table 100 | grep -q "^local.*dev lo"; then
ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null
fi
# Create TCP chain in NAT table
$IPT -N SS_SPEC_WAN_FW 2>/dev/null
$IPT -F SS_SPEC_WAN_FW
for net in \
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.168.0.0/16 224.0.0.0/4 240.0.0.0/4
do
$IPT -A SS_SPEC_WAN_FW -d "$net" -j RETURN
done
$IPT -A SS_SPEC_WAN_FW -p tcp $PROXY_PORTS -j REDIRECT --to-ports "$local_port" 2>/dev/null || {
loger 3 "Can't redirect TCP, please check the iptables."
exit 1
}
return $?
}
ac_rule() {
if [ "$USE_NFT" = "1" ]; then
ac_rule_nft
else
ac_rule_iptables
fi
return $?
}
ac_rule_nft() {
local MATCH_SET=""
if [ -n "$LAN_AC_IP" ]; then
# Create LAN access control set if needed
if ! $NFT list set inet ss_spec ss_spec_lan_ac >/dev/null 2>&1; then
$NFT add set inet ss_spec ss_spec_lan_ac '{ type ipv4_addr; flags interval; }' 2>/dev/null
else
$NFT flush set inet ss_spec ss_spec_lan_ac 2>/dev/null
fi
for ip in ${LAN_AC_IP#?}; do
[ -n "$ip" ] && $NFT add element inet ss_spec ss_spec_lan_ac "{ $ip }" 2>/dev/null
done
case "${LAN_AC_IP%${LAN_AC_IP#?}}" in
w | W)
MATCH_SET="ip saddr @ss_spec_lan_ac"
;;
b | B)
MATCH_SET="ip saddr != @ss_spec_lan_ac"
;;
*)
loger 3 "Bad argument \`-a $LAN_AC_IP\`."
return 2
;;
esac
fi
# Create ss_spec_prerouting tcp chain
if ! $NFT list chain inet ss_spec ss_spec_prerouting >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_prerouting '{ type nat hook prerouting priority 0; policy accept; }'
fi
$NFT flush chain inet ss_spec ss_spec_prerouting 2>/dev/null
# Exclude special local addresses
if $NFT list chain inet ss_spec ss_spec_prerouting >/dev/null 2>&1; then
for net in 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.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do
$NFT add rule inet ss_spec ss_spec_prerouting ip daddr $net return 2>/dev/null
done
fi
# Temporarily comment IPV6 for future enablement
#if $NFT list chain inet ss_spec ss_spec_prerouting >/dev/null 2>&1; then
# for net in ::1/128 fe80::/10 fc00::/7 ff00::/8 ::/128 ::ffff:0:0/96; do
# $NFT add rule inet ss_spec ss_spec_prerouting ip6 daddr $net return 2>/dev/null
# done
#fi
# Build a rule in the prerouting hook chain that jumps to business chain with conditions
if [ -n "$PROXY_PORTS" ]; then
PORTS_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
if [ -n "$PORTS_ARGS" ]; then
TCP_EXT_ARGS="meta l4proto tcp tcp dport { $PORTS_ARGS }"
fi
else
TCP_EXT_ARGS="meta l4proto tcp"
fi
# Block UDP port 443 when TPROXY not Enable
if [ -z "$TPROXY" ]; then
# Add UDP 443 block rule
if [ -z "$Interface" ]; then
if [ -n "$MATCH_SET" ]; then
$NFT add rule inet ss_spec ss_spec_prerouting meta l4proto udp $MATCH_SET udp dport 443 drop comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule inet ss_spec ss_spec_prerouting meta l4proto udp udp dport 443 drop comment "\"$TAG\"" 2>/dev/null
fi
else
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
if [ -n "$IFNAME" ]; then
if [ -n "$MATCH_SET" ]; then
$NFT add rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" meta l4proto udp $MATCH_SET udp dport 443 drop comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" meta l4proto udp udp dport 443 drop comment "\"$TAG\"" 2>/dev/null
fi
fi
done
fi
fi
if [ -z "$Interface" ]; then
# generic prerouting jump already exists (see ipset_nft), but if we have MATCH_SET_CONDITION we add a more specific rule
if [ -n "$MATCH_SET" ]; then
# add a more specific rule at the top of ss_spec_prerouting
$NFT add rule inet ss_spec ss_spec_prerouting $TCP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule inet ss_spec ss_spec_prerouting $TCP_EXT_ARGS jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
fi
else
# For each Interface, find its actual ifname and add an iifname-limited prerouting rule
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
if [ -n "$IFNAME" ]; then
if [ -n "$MATCH_SET" ]; then
$NFT add rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" $TCP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" $TCP_EXT_ARGS jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
fi
fi
done
fi
case "$OUTPUT" in
1)
# Create ss_spec_output tcp chain
if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
fi
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
# Exclude special local addresses
if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
for net in 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.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do
$NFT add rule inet ss_spec ss_spec_output ip daddr $net return 2>/dev/null
done
fi
# Temporarily comment IPV6 for future enablement
#if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
# for net in ::1/128 fe80::/10 fc00::/7 ff00::/8 ::/128 ::ffff:0:0/96; do
# $NFT add rule inet ss_spec ss_spec_output ip6 daddr $net return 2>/dev/null
# done
#fi
# create output hook chain & route output traffic into router chain
$NFT add rule inet ss_spec ss_spec_output $TCP_EXT_ARGS jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
;;
2)
# Create ss_spec_output tcp chain
if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority 0; policy accept; }'
fi
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
# Exclude special local addresses
if $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
for net in 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.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do
$NFT add rule inet ss_spec ss_spec_output ip daddr $net return 2>/dev/null
done
fi
# router mode output chain: create ssr_gen_router set & router chain
$NFT add set inet ss_spec ssr_gen_router '{ type ipv4_addr; flags interval; }' 2>/dev/null
for ip in $(gen_spec_iplist); do
[ -n "$ip" ] && $NFT add element inet ss_spec ssr_gen_router "{ $ip }" 2>/dev/null
done
if ! $NFT list chain inet ss_spec ss_spec_router >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_router 2>/dev/null
fi
$NFT flush chain inet ss_spec ss_spec_router 2>/dev/null
$NFT add rule inet ss_spec ss_spec_router ip daddr @ssr_gen_router return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_router jump ss_spec_wan_fw 2>/dev/null
$NFT add rule inet ss_spec ss_spec_output $TCP_EXT_ARGS jump ss_spec_router comment "\"$TAG\"" 2>/dev/null
;;
esac
return 0
}
ac_rule_iptables() {
local MATCH_SET=""
if [ -n "$LAN_AC_IP" ]; then
case "${LAN_AC_IP%${LAN_AC_IP#?}}" in
w | W)
MATCH_SET="-m set --match-set ss_spec_lan_ac src"
;;
b | B)
MATCH_SET="-m set ! --match-set ss_spec_lan_ac src"
;;
*)
loger 3 "Bad argument \`-a $LAN_AC_IP\`."
return 2
;;
esac
fi
ipset -! -R <<-EOF || return 1
create ss_spec_lan_ac hash:net
$(for ip in ${LAN_AC_IP#?}; do echo "add ss_spec_lan_ac $ip"; done)
EOF
# Block UDP port 443 when TPROXY not Enable
if [ -z "$TPROXY" ]; then
# Add UDP 443 block rule
if [ -z "$Interface" ]; then
$ipt -I PREROUTING 1 -p udp $EXT_ARGS $MATCH_SET --dport 443 -j DROP -m comment --comment "$TAG"
else
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
if [ -n "$IFNAME" ]; then
$ipt -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p udp $EXT_ARGS $MATCH_SET --dport 443 -j DROP -m comment --comment "$TAG"
fi
done
fi
fi
if [ -z "$Interface" ]; then
$IPT -I PREROUTING 1 -p tcp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_WAN_AC
else
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
if [ -n "$IFNAME" ]; then
$IPT -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p tcp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_WAN_AC
fi
done
fi
case "$OUTPUT" in
1)
$IPT -I OUTPUT 1 -p tcp $EXT_ARGS -m comment --comment "$TAG" -j SS_SPEC_WAN_AC
;;
2)
ipset -! -R <<-EOF || return 1
create ssr_gen_router hash:net
$(gen_spec_iplist | sed -e "s/^/add ssr_gen_router /")
EOF
$IPT -N SS_SPEC_ROUTER 2>/dev/null
$IPT -F SS_SPEC_ROUTER 2>/dev/null
$IPT -A SS_SPEC_ROUTER -m set --match-set ssr_gen_router dst -j RETURN && \
$IPT -A SS_SPEC_ROUTER -j SS_SPEC_WAN_FW
$IPT -I OUTPUT 1 -p tcp -m comment --comment "$TAG" -j SS_SPEC_ROUTER
;;
esac
return $?
}
tp_rule() {
[ -n "$TPROXY" ] || return 0
if [ "$USE_NFT" = "1" ]; then
tp_rule_nft
else
tp_rule_iptables
fi
return $?
}
tp_rule_nft() {
# set up routing table for tproxy
if ! ip rule show | grep -Eq "fwmark 0x0*1.*lookup 100"; then
ip rule add fwmark 0x01/0x01 table 100 2>/dev/null
fi
if ! ip route show table 100 | grep -q "^local.*dev lo"; then
ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null
fi
# create mangle table and tproxy chain
if ! $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then
$NFT add table ip ss_spec_mangle 2>/dev/null
fi
local MATCH_SET=""
if [ -n "$PROXY_PORTS" ]; then
PORTS_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
if [ -n "$PORTS_ARGS" ]; then
EXT_ARGS="udp dport { $PORTS_ARGS }"
else
EXT_ARGS=""
fi
fi
if [ -n "$LAN_AC_IP" ]; then
# Create LAN access control set if needed
if ! $NFT list set ip ss_spec_mangle ss_spec_lan_ac >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle ss_spec_lan_ac '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
else
$NFT flush set ip ss_spec_mangle ss_spec_lan_ac 2>/dev/null
fi
for ip in ${LAN_AC_IP#?}; do
[ -n "$ip" ] && $NFT add element ip ss_spec_mangle ss_spec_lan_ac "{ $ip }" 2>/dev/null
done
case "${LAN_AC_IP%${LAN_AC_IP#?}}" in
w | W)
MATCH_SET="ip saddr @ss_spec_lan_ac"
;;
b | B)
MATCH_SET="ip saddr != @ss_spec_lan_ac"
;;
*)
loger 3 "Bad argument \`-a $LAN_AC_IP\`."
return 2
;;
esac
fi
# Create necessary collections
for setname in china gmlan fplan bplan whitelist; do
if ! $NFT list set ip ss_spec_mangle $setname >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle $setname '{ type ipv4_addr; flags interval; auto-merge; }'
else
$NFT flush set ip ss_spec_mangle $setname 2>/dev/null
fi
done
# Bulk import china ip list safely (avoid huge single element limitation)
if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then
$NFT add element ip ss_spec_mangle china "{ $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') }" 2>/dev/null
fi
# Bulk import xhttp ip list into nft whitelist (server + shunt)
if [ -f "${xhttp_ip:=/etc/ssrplus/xhttp_address.txt}" ]; then
$NFT add element ip ss_spec_mangle whitelist "{ $(tr '\n' ',' < "${xhttp_ip}" | sed 's/,$//') }" 2>/dev/null
fi
# use priority mangle for compatibility with other rules
if ! $NFT list chain ip ss_spec_mangle ss_spec_tproxy >/dev/null 2>&1; then
$NFT add chain ip ss_spec_mangle ss_spec_tproxy 2>/dev/null
else
$NFT flush chain ip ss_spec_mangle ss_spec_tproxy 2>/dev/null
fi
if $NFT list chain ip ss_spec_mangle ss_spec_tproxy >/dev/null 2>&1; then
for net in 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.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do
$NFT add rule ip ss_spec_mangle ss_spec_tproxy ip daddr $net return 2>/dev/null
done
fi
# Temporarily comment IPV6 for future enablement
#if $NFT list chain ip ss_spec_mangle ss_spec_tproxy >/dev/null 2>&1; then
# for net in ::1/128 fe80::/10 fc00::/7 ff00::/8 fe80::/10 ::/128 ::ffff:0:0/96; do
# $NFT add rule ip ss_spec_mangle ss_spec_tproxy ip6 daddr $net return 2>/dev/null
# done
#fi
# basic return rules in tproxy chain
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp dport 53 return 2>/dev/null
# avoid redirecting to udp server address
if [ -n "$server" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp dport != 53 ip daddr "$server" return 2>/dev/null
fi
# if server != SERVER add SERVER to whitelist set (so tproxy won't touch it)
if [ -n "$server" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy ip daddr "$server" return 2>/dev/null
fi
if [ -n "$SERVER" ] && [ "$server" != "$SERVER" ]; then
$NFT add element ip ss_spec_mangle whitelist "{ $SERVER }" 2>/dev/null
fi
# access control and tproxy rules
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @bplan return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @fplan counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
# Handle different run modes for nftables
case "$RUNMODE" in
router)
if ! $NFT list set ip ss_spec_mangle ss_spec_wan_ac >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle ss_spec_wan_ac '{ type ipv4_addr; flags interval; auto-merge; }'
else
$NFT flush set ip ss_spec_mangle ss_spec_wan_ac 2>/dev/null
fi
# Add special IP ranges to WAN AC set
for ip in $(gen_spec_iplist); do
[ -n "$ip" ] && $NFT add element ip ss_spec_mangle ss_spec_wan_ac "{ $ip }" 2>/dev/null
done
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @ss_spec_wan_ac return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @china return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp udp dport 80 counter drop comment "\"$TAG\"" 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp udp dport 443 counter drop comment "\"$TAG\"" 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr != @ss_spec_wan_ac counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
gfw)
if ! $NFT list set ip ss_spec_mangle gfwlist >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle gfwlist '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
fi
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @china return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp udp dport 80 counter drop comment "\"$TAG\"" 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp udp dport 443 counter drop comment "\"$TAG\"" 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @gfwlist counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
oversea)
if ! $NFT list set ip ss_spec_mangle oversea >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle oversea '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
fi
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @oversea counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
all)
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
esac
# finally, ensure prerouting hook entry to jump to tproxy chain
if ! $NFT list chain ip ss_spec_mangle prerouting >/dev/null 2>&1; then
$NFT add chain ip ss_spec_mangle prerouting '{ type filter hook prerouting priority mangle; policy accept; }'
fi
# add prerouting jump (idempotent)
if [ -z "$Interface" ]; then
# Global rules
if [ -n "$MATCH_SET" ]; then
$NFT add rule ip ss_spec_mangle prerouting meta l4proto udp $EXT_ARGS $MATCH_SET jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule ip ss_spec_mangle prerouting meta l4proto udp $EXT_ARGS jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
fi
else
# Specific interface
for name in $Interface; do
IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
if [ -n "$IFNAME" ]; then
if [ -n "$MATCH_SET" ]; then
$NFT add rule ip ss_spec_mangle prerouting meta iifname "$IFNAME" meta l4proto udp $EXT_ARGS $MATCH_SET jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule ip ss_spec_mangle prerouting meta iifname "$IFNAME" meta l4proto udp $EXT_ARGS jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
fi
fi
done
fi
return $?
}
tp_rule_iptables() {
# set up routing table for tproxy
if ! ip rule show | grep -Eq "fwmark 0x0*1.*lookup 100"; then
ip rule add fwmark 0x01/0x01 table 100 2>/dev/null
fi
if ! ip route show table 100 | grep -q "^local.*dev lo"; then
ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null
fi
$ipt -N SS_SPEC_TPROXY 2>/dev/null
$ipt -F SS_SPEC_TPROXY
$ipt -A SS_SPEC_TPROXY -p udp --dport 53 -j RETURN
local MATCH_SET=""
if [ -n "$LAN_AC_IP" ]; then
case "${LAN_AC_IP%${LAN_AC_IP#?}}" in
w | W)
MATCH_SET_UDP="-m set --match-set ss_spec_lan_ac src"
;;
b | B)
MATCH_SET_UDP="-m set ! --match-set ss_spec_lan_ac src"
;;
*)
loger 3 "Bad argument \`-a $LAN_AC_IP\`."
return 2
;;
esac
fi
ipset -! -R <<-EOF || return 1
create ss_spec_lan_ac hash:net
$(for ip in ${LAN_AC_IP#?}; do echo "add ss_spec_lan_ac $ip"; done)
EOF
for net in \
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.168.0.0/16 224.0.0.0/4 240.0.0.0/4
do
$ipt -A SS_SPEC_TPROXY -p udp -d "$net" -j RETURN
done
$ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN
[ "$server" != "$SERVER" ] && ipset -! add whitelist "$SERVER"
if [ -f "${xhttp_ip:=/etc/ssrplus/xhttp_address.txt}" ]; then
while IFS= read -r ip; do
[ -n "$ip" ] && ipset add whitelist "$ip" -exist
done < "$xhttp_ip"
fi
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set bplan src -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set fplan src -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
case "$RUNMODE" in
router)
ipset -! -R <<-EOF || return 1
create ss_spec_wan_ac hash:net
$(gen_spec_iplist | sed -e "s/^/add ss_spec_wan_ac /")
EOF
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set ss_spec_wan_ac dst -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set china dst -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp --dport 80 -j DROP
$ipt -A SS_SPEC_TPROXY -p udp --dport 443 -j DROP
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set ! --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set ! --match-set ss_spec_wan_ac dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
;;
gfw)
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set china dst -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp --dport 80 -j DROP
$ipt -A SS_SPEC_TPROXY -p udp --dport 443 -j DROP
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set gfwlist dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set ! --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
;;
oversea)
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set oversea src -m dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
;;
all)
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
;;
esac
if [ -z "$Interface" ]; then
$ipt -I PREROUTING 1 -p udp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_TPROXY
else
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
[ -n "$IFNAME" ] && $ipt -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p udp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_TPROXY
done
fi
return $?
}
get_wan_ip() {
cat <<-EOF | grep -E "^([0-9]{1,3}\.){3}[0-9]{1,3}"
$server
$SERVER
$WAN_BP_IP
EOF
}
gen_spec_iplist() {
cat <<-EOF
0.0.0.0/8
10.0.0.0/8
100.64.0.0/10
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.18.0.0/15
198.51.100.0/24
203.0.113.0/24
224.0.0.0/4
240.0.0.0/4
255.255.255.255
$(get_wan_ip)
EOF
}
gen_include() {
[ -n "$FWI" ] || return 0
if [ "$USE_NFT" = "1" ]; then
gen_include_nft
else
gen_include_iptables
fi
return $?
}
# Modified gen_include_nft to call persistence function
gen_include_nft() {
# Generate nftables include file for firewall4
[ -n "$FWI" ] && echo '#!/bin/sh' >"$FWI"
cat <<-EOF >>"$FWI"
# Clear existing ss_spec tables
nft delete table inet ss_spec 2>/dev/null
nft delete table ip ss_spec 2>/dev/null
nft delete table ip ss_spec_mangle 2>/dev/null
# Restore shadowsocks nftables rules from persistent file
if [ -f "/usr/share/nftables.d/ruleset-post/99-shadowsocksr.nft" ]; then
nft -f /usr/share/nftables.d/ruleset-post/99-shadowsocksr.nft
else
# Fallback: restore from current ruleset (filtered)
nft list ruleset | awk '/^table (inet|ip) ss_spec/{flag=1} /^table / && !/^table (inet|ip) ss_spec/{flag=0} flag' | nft -f -
fi
EOF
chmod +x "$FWI"
}
gen_include_iptables() {
extract_rules() {
echo "*$1"
iptables-save -t $1 | grep SS_SPEC_ | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
echo 'COMMIT'
}
cat <<-EOF >>$FWI
iptables-save -c | grep -v "SS_SPEC" | iptables-restore -c
iptables-restore -n <<-EOT
$(extract_rules nat)
$(extract_rules mangle)
EOT
EOF
}
# Check nftables rules status
check_nftables_status() {
if [ "$USE_NFT" != "1" ]; then
echo "NFTables not in use"
return 0
fi
# Check if ss_spec tables exist
if ! $NFT list table inet ss_spec >/dev/null 2>&1 && \
! $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then
echo "ss_spec tables missing in nftables"
return 1
fi
# Check if basic rules exist
if ! $NFT list table inet ss_spec 2>/dev/null | grep -q "chain.*ss_spec_wan_ac" || \
! $NFT list table inet ss_spec 2>/dev/null | grep -q "jump.*ss_spec_wan_fw"; then
echo "Basic SSR rules missing"
return 1
fi
echo "NFTables rules status: OK"
return 0
}
# Compare current rules with persistence rules
compare_rules() {
if [ "$USE_NFT" != "1" ]; then
return 1 # NFTables not used, need update
fi
# If no persistence file, update persistence file
if [ ! -f "$NFTABLES_RULES_FILE" ]; then
loger 6 "No persistence file found, update needed"
return 1 # Need to update persistence file
fi
# Check if ss_spec tables exist
if ! $NFT list table inet ss_spec >/dev/null 2>&1 && \
! $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then
loger 6 "ss_spec tables missing, update needed"
return 1 # Need to update ss_spec table
fi
# Generate temporary file for current rules
local temp_file=$(mktemp)
local rules_file=$(mktemp)
loger 7 "DEBUG: Temporary file path: $rules_file"
# Export current rules to temporary file
$NFT list ruleset | awk '
/^table (inet ss_spec|ip ss_spec_mangle)/ {flag=1}
/^table / && !/^table (inet ss_spec|ip ss_spec_mangle)/ {flag=0}
flag
' > "$rules_file" 2>/dev/null
# Check if current rules were exported successfully
if [ ! -s "$rules_file" ] || ! grep -q "table" "$rules_file" 2>/dev/null; then
loger 4 "Failed to export current rules"
rm -f "$temp_file" "$rules_file"
return 1 # Export failed, need update
fi
# Compare current rules with rules in persistence file
if ! cmp -s "$rules_file" "$NFTABLES_RULES_FILE"; then
loger 6 "Rules differ, update needed"
rm -f "$temp_file" "$rules_file"
return 1 # Need update
fi
rm -f "$temp_file" "$rules_file"
loger 6 "Rules unchanged, no update needed"
return 0 # No update needed
}
# Auto-update persistence rules
persist_nftables_rules() {
if [ "$USE_NFT" != "1" ]; then
return 0
fi
# If mode unchanged and persistence file exists, skip update
if [ "$MODE_CHANGED" = "0" ] && [ -f "$NFTABLES_RULES_FILE" ]; then
loger 6 "Mode unchanged and persistence file exists, skipping update"
return 0
fi
# Force update: skip comparison check and delete old file
if [ "$FORCE_UPDATE" = "1" ]; then
loger 6 "Force update requested, removing old persistence file"
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
# Non-force update: compare rules
elif [ -f "$NFTABLES_RULES_FILE" ]; then
if compare_rules; then
loger 6 "Rules unchanged, skipping persistence update"
return 0
fi
fi
# Ensure directory exists
mkdir -p "$NFTABLES_RULES_DIR" 2>/dev/null
# Generate nftables rule file
cat <<-'EOF' >>$NFTABLES_RULES_FILE
#!/usr/sbin/nft -f
# ShadowsocksR nftables rules
# Generated by ssr-rules script
EOF
echo "# Auto-updated: $(date)" >> "$NFTABLES_RULES_FILE"
echo "# Runmode: ${RUNMODE:-router}" >> "$NFTABLES_RULES_FILE"
echo "# Server: $server, Port: $local_port" >> "$NFTABLES_RULES_FILE"
echo "# WAN_BP_IP: $WAN_BP_IP" >> "$NFTABLES_RULES_FILE"
echo "# LAN_AC_IP: $LAN_AC_IP" >> "$NFTABLES_RULES_FILE"
echo "# LAN_BP_IP: $LAN_BP_IP" >> "$NFTABLES_RULES_FILE"
echo "# WAN_FW_IP: $WAN_FW_IP" >> "$NFTABLES_RULES_FILE"
echo "# LAN_FP_IP: $LAN_FP_IP" >> "$NFTABLES_RULES_FILE"
echo "# LAN_GM_IP: $LAN_GM_IP" >> "$NFTABLES_RULES_FILE"
echo "" >> "$NFTABLES_RULES_FILE"
local HAS_RULES=0
# Export each table separately
if $NFT list table inet ss_spec >/dev/null 2>&1; then
loger 6 "Exporting table inet ss_spec"
{
echo ""
echo "# inet ss_spec table for main rules"
$NFT list table inet ss_spec 2>/dev/null
} >> "$NFTABLES_RULES_FILE"
HAS_RULES=1
fi
if $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then
loger 6 "Exporting table ip ss_spec_mangle"
{
echo ""
echo "# ip ss_spec_mangle table for TPROXY rules"
$NFT list table ip ss_spec_mangle 2>/dev/null
} >> "$NFTABLES_RULES_FILE"
HAS_RULES=1
fi
# Check if rules were exported successfully
if [ $HAS_RULES -eq 0 ] || [ ! -s "$NFTABLES_RULES_FILE" ] || ! grep -q "table" "$NFTABLES_RULES_FILE" 2>/dev/null; then
loger 4 "No ss_spec nftables rules found to persist"
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
return 1
fi
# Set file permissions
chmod 644 "$NFTABLES_RULES_FILE" 2>/dev/null
# Log success information
local TABLES=$(grep "^table" "$NFTABLES_RULES_FILE" | awk '{print $2 " " $3}' | tr '\n' ',' | sed 's/,$//')
loger 5 "NFTables rules persisted to $NFTABLES_RULES_FILE (Tables: $TABLES)"
return 0
}
# Auto-update daemon
start_auto_update_daemon() {
if [ "$USE_NFT" != "1" ] || [ "$AUTO_UPDATE_INTERVAL" = "0" ]; then
return 0
fi
loger 6 "Starting nftables rules auto-update daemon"
# Stop already running daemon
stop_auto_update_daemon
# Start daemon directly in background
(
logger -t ssr-rules[daemon] "Auto-update daemon started - PID: $$"
echo $$ > "/var/run/ssr-rules-daemon.pid"
while true; do
sleep 300
if [ -x "/usr/bin/ssr-rules" ]; then
if /usr/bin/ssr-rules -C >/dev/null 2>&1; then
logger -t ssr-rules[daemon] "Rules changed or missing, updating persistence"
if /usr/bin/ssr-rules -P >/dev/null 2>&1; then
logger -t ssr-rules[daemon] "Persistence rules updated successfully"
else
logger -t ssr-rules[daemon] "Failed to update persistence"
fi
else
logger -t ssr-rules[daemon] "Rules status OK, no update needed"
fi
else
logger -t ssr-rules[daemon] "Script not found, exiting daemon"
exit 1
fi
done
) &
local DAEMON_PID=$!
sleep 2
if kill -0 "$DAEMON_PID" 2>/dev/null; then
loger 6 "Auto-update daemon started with PID: $DAEMON_PID"
return 0
else
loger 3 "Auto-update daemon failed to start"
return 1
fi
}
# Stop auto-update daemon function
stop_auto_update_daemon() {
local PID_FILE="/var/run/ssr-rules-daemon.pid"
if [ -f "$PID_FILE" ]; then
local DAEMON_PID=$(cat "$PID_FILE" 2>/dev/null)
if [ -n "$DAEMON_PID" ] && kill -0 "$DAEMON_PID" 2>/dev/null; then
kill "$DAEMON_PID" 2>/dev/null
loger 6 "Stopped auto-update daemon (PID: $DAEMON_PID)"
fi
rm -f "$PID_FILE" 2>/dev/null
fi
loger 6 "Auto-update daemon stopped"
}
# Force update persistence rules function
force_update_persistence() {
if [ "$USE_NFT" != "1" ]; then
echo "NFTables not in use"
return 0
fi
# Remove existing rule file to ensure recreation
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
# Call persistence function
if persist_nftables_rules; then
loger 5 "Persistence update completed successfully"
return 0
else
loger 3 "Persistence update failed"
return 1
fi
}
# Restore rules from persistence file
restore_from_persistence() {
if [ "$USE_NFT" != "1" ]; then
loger 3 "NFTables not in use, cannot restore rules"
return 1
fi
if [ ! -f "$NFTABLES_RULES_FILE" ]; then
loger 4 "Persistence file not found: $NFTABLES_RULES_FILE"
return 1
fi
loger 6 "Restoring rules from persistence file"
# Cleanup existing rules
flush_r
# Restore rules from file
if $NFT -f "$NFTABLES_RULES_FILE" 2>/dev/null; then
loger 5 "Rules restored successfully from persistence file"
return 0
else
loger 4 "Failed to restore rules from persistence file"
return 1
fi
}
while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczAKPCRXh" arg; do
case "$arg" in
m)
Interface=$OPTARG
;;
s)
server=$OPTARG
;;
l)
local_port=$OPTARG
;;
S)
SERVER=$OPTARG
;;
L)
LOCAL_PORT=$OPTARG
;;
i)
IGNORE_LIST=$OPTARG
;;
e)
EXT_ARGS=$OPTARG
;;
a)
LAN_AC_IP=$OPTARG
;;
B)
LAN_BP_IP=$OPTARG
;;
b)
WAN_BP_IP=$(for ip in $OPTARG; do echo "$ip"; done)
;;
w)
WAN_FW_IP=$OPTARG
;;
p)
LAN_FP_IP=$OPTARG
;;
G)
LAN_GM_IP=$OPTARG
;;
D)
PROXY_PORTS=$OPTARG
;;
F)
SHUNT_PORT=$OPTARG
;;
N)
SHUNT_IP=$OPTARG
;;
M)
SHUNT_PROXY=$OPTARG
;;
I)
SHUNT_LIST=$OPTARG
;;
o)
OUTPUT=1
;;
O)
OUTPUT=2
;;
u)
TPROXY=1
;;
U)
TPROXY=2
;;
g)
RUNMODE=gfw
;;
r)
RUNMODE=router
;;
c)
RUNMODE=oversea
;;
z)
RUNMODE=all
;;
# New persistence management options
A)
ENABLE_AUTO_UPDATE=1
;;
K)
STOP_AUTO_UPDATE=1
;;
P)
FORCE_UPDATE=1
;;
C)
CHECK_STATUS=1
;;
R)
RESTORE_RULES=1
;;
X)
CLEANUP_PERSISTENCE=1
;;
f)
FLUSH_RULES=1
;;
h)
usage 0
;;
esac
done
# First process options that need immediate exit
if [ "$CHECK_STATUS" = "1" ]; then
check_nftables_status
exit $?
fi
if [ "$STOP_AUTO_UPDATE" = "1" ]; then
stop_auto_update_daemon
exit 0
fi
# Only -X option, cleanup and exit
if [ "$CLEANUP_PERSISTENCE" = "1" ] && [ "$FLUSH_RULES" != "1" ] && [ -z "$server" ] && [ -z "$local_port" ] && \
[ "$FORCE_UPDATE" != "1" ] && [ "$RESTORE_RULES" != "1" ] && [ "$ENABLE_AUTO_UPDATE" != "1" ]; then
cleanup_persistence_files
exit $?
fi
# Check if there are persistence management options only
PERSISTENCE_ONLY=0
if [ -z "$server" ] && [ -z "$local_port" ] && [ "$FLUSH_RULES" != "1" ]; then
if [ "$FORCE_UPDATE" = "1" ] || [ "$RESTORE_RULES" = "1" ] || [ "$ENABLE_AUTO_UPDATE" = "1" ] || [ "$CLEANUP_PERSISTENCE" = "1" ]; then
PERSISTENCE_ONLY=1
else
usage 2
fi
fi
# Handle persistence management options
if [ "$PERSISTENCE_ONLY" = "1" ]; then
if [ "$FORCE_UPDATE" = "1" ]; then
force_update_persistence
exit $?
fi
if [ "$RESTORE_RULES" = "1" ]; then
restore_from_persistence
exit $?
fi
if [ "$ENABLE_AUTO_UPDATE" = "1" ]; then
start_auto_update_daemon
exit $?
fi
fi
# Force flush rules
if [ "$FLUSH_RULES" = "1" ]; then
flush_r
# If only -f option, then exit
if [ -z "$server" ] && [ -z "$local_port" ] && [ "$FORCE_UPDATE" != "1" ] && \
[ "$RESTORE_RULES" != "1" ] && [ "$ENABLE_AUTO_UPDATE" != "1" ] && \
[ "$CLEANUP_PERSISTENCE" != "1" ]; then
exit 0
fi
fi
# Restore rules from persistence file (before rule application)
if [ "$RESTORE_RULES" = "1" ]; then
restore_from_persistence
if [ $? -ne 0 ]; then
loger 3 "Failed to restore from persistence, continuing with rule application"
fi
fi
# Run mode change
runmode_change() {
local mode_file="/tmp/.ssr_run_mode"
local new_mode=""
local old_mode=""
# Get mode from parameters
if [ -n "$1" ]; then
new_mode="$1"
fi
# Read previous run mode from file
if [ -f "$mode_file" ]; then
old_mode=$(cat "$mode_file" 2>/dev/null)
fi
# Compare if mode changed
if [ "$old_mode" = "$new_mode" ] && [ -n "$old_mode" ]; then
# Mode unchanged
echo "$new_mode" > "$mode_file" # Update file timestamp
loger 6 "Runmode unchanged: $new_mode"
return 1 # Return 1 means unchanged
else
# Mode changed or first run
echo "$new_mode" > "$mode_file"
if [ -n "$old_mode" ]; then
loger 6 "Runmode changed from '$old_mode' to '$new_mode'"
else
loger 6 "Runmode set to '$new_mode'"
fi
return 0 # Return 0 means changed
fi
}
# Main process
if [ -n "$server" ] && [ -n "$local_port" ]; then
if ! echo "$local_port" | grep -qE '^[0-9]+$'; then
loger 3 "Invalid local port: $local_port"
exit 1
fi
case "$TPROXY" in
1)
SERVER=$server
LOCAL_PORT=$local_port
;;
2)
: ${SERVER:?"You must assign an ip for the udp relay server."}
: ${LOCAL_PORT:?"You must assign a port for the udp relay server."}
;;
esac
if [ "$USE_NFT" = "1" ]; then
# NFTables
# Save previous TPROXY state file
TPROXY_STATE_FILE="/tmp/.last_tproxy"
if [ -f "$TPROXY_STATE_FILE" ]; then
LAST_TPROXY=$(cat "$TPROXY_STATE_FILE")
else
LAST_TPROXY=""
fi
# Save previous PROXY_PORTS state
PROXY_PORTS_STATE_FILE="/tmp/.last_proxy_ports"
if [ -f "$PROXY_PORTS_STATE_FILE" ]; then
LAST_PROXY_PORTS=$(cat "$PROXY_PORTS_STATE_FILE")
else
LAST_PROXY_PORTS=""
fi
# Save previous WAN_BP_IP state
WAN_BP_IP_STATE_FILE="/tmp/.last_wan_bp_ip"
if [ -f "$WAN_BP_IP_STATE_FILE" ]; then
LAST_WAN_BP_IP=$(cat "$WAN_BP_IP_STATE_FILE")
else
LAST_WAN_BP_IP=""
fi
# Save previous LAN_AC_IP state
LAN_AC_IP_STATE_FILE="/tmp/.last_lan_ac_ip"
if [ -f "$LAN_AC_IP_STATE_FILE" ]; then
LAST_LAN_AC_IP=$(cat "$LAN_AC_IP_STATE_FILE")
else
LAST_LAN_AC_IP=""
fi
# Save previous LAN_BP_IP state
LAN_BP_IP_STATE_FILE="/tmp/.last_lan_bp_ip"
if [ -f "$LAN_BP_IP_STATE_FILE" ]; then
LAST_LAN_BP_IP=$(cat "$LAN_BP_IP_STATE_FILE")
else
LAST_LAN_BP_IP=""
fi
# Save previous WAN_FW_IP state
WAN_FW_IP_STATE_FILE="/tmp/.last_wan_fw_ip"
if [ -f "$WAN_FW_IP_STATE_FILE" ]; then
LAST_WAN_FW_IP=$(cat "$WAN_FW_IP_STATE_FILE")
else
LAST_WAN_FW_IP=""
fi
# Save previous LAN_FP_IP state
LAN_FP_IP_STATE_FILE="/tmp/.last_lan_fp_ip"
if [ -f "$LAN_FP_IP_STATE_FILE" ]; then
LAST_LAN_FP_IP=$(cat "$LAN_FP_IP_STATE_FILE")
else
LAST_LAN_FP_IP=""
fi
# Save previous LAN_GM_IP state
LAN_GM_IP_STATE_FILE="/tmp/.last_lan_gm_ip"
if [ -f "$LAN_GM_IP_STATE_FILE" ]; then
LAST_LAN_GM_IP=$(cat "$LAN_GM_IP_STATE_FILE")
else
LAST_LAN_GM_IP=""
fi
# Check for changes in the existence and content of the server XHTTP address file
XHTTP_FILE_STATE_FILE="/tmp/.last_xhttp_file"
XHTTP_FILE_HASH_FILE="/tmp/.last_xhttp_hash"
# Get the current server XHTTP file status
XHTTP_FILE_EXISTS=0
XHTTP_FILE_HASH=""
if [ -f "/etc/ssrplus/xhttp_address.txt" ]; then
XHTTP_FILE_EXISTS=1
XHTTP_FILE_HASH=$(md5sum /etc/ssrplus/xhttp_address.txt 2>/dev/null | awk '{print $1}')
fi
# Read the previous server XHTTP file status and hash
if [ -f "$XHTTP_FILE_STATE_FILE" ]; then
LAST_XHTTP_FILE_EXISTS=$(cat "$XHTTP_FILE_STATE_FILE")
else
LAST_XHTTP_FILE_EXISTS=""
fi
if [ -f "$XHTTP_FILE_HASH_FILE" ]; then
LAST_XHTTP_FILE_HASH=$(cat "$XHTTP_FILE_HASH_FILE")
else
LAST_XHTTP_FILE_HASH=""
fi
# STEP 1: Check if TPROXY has value (1 or 2)
if [ "$TPROXY" = "1" ] || [ "$TPROXY" = "2" ]; then
TPROXY_HAS_VALUE=1
else
TPROXY_HAS_VALUE=0
fi
if [ "$LAST_TPROXY" = "1" ] || [ "$LAST_TPROXY" = "2" ]; then
LAST_HAS_VALUE=1
else
LAST_HAS_VALUE=0
fi
# STEP 2: Check if PROXY_PORTS has value (non-empty string)
if [ -n "${PROXY_PORTS// }" ]; then
PROXY_HAS_VALUE=1
else
PROXY_HAS_VALUE=0
fi
if [ -n "${LAST_PROXY_PORTS// }" ]; then
LAST_PROXY_HAS_VALUE=1
else
LAST_PROXY_HAS_VALUE=0
fi
# STEP 2.5: Check if any IP list has changed
ANY_IP_LIST_CHANGED=0
# Check WAN_BP_IP
check_ip_list_changed "$WAN_BP_IP" "$LAST_WAN_BP_IP" "WAN_BP_IP"
if [ $? -eq 1 ]; then
ANY_IP_LIST_CHANGED=1
fi
# Check LAN_AC_IP
check_ip_list_changed "$LAN_AC_IP" "$LAST_LAN_AC_IP" "LAN_AC_IP"
if [ $? -eq 1 ]; then
ANY_IP_LIST_CHANGED=1
fi
# Check LAN_BP_IP
check_ip_list_changed "$LAN_BP_IP" "$LAST_LAN_BP_IP" "LAN_BP_IP"
if [ $? -eq 1 ]; then
ANY_IP_LIST_CHANGED=1
fi
# Check WAN_FW_IP
check_ip_list_changed "$WAN_FW_IP" "$LAST_WAN_FW_IP" "WAN_FW_IP"
if [ $? -eq 1 ]; then
ANY_IP_LIST_CHANGED=1
fi
# Check LAN_FP_IP
check_ip_list_changed "$LAN_FP_IP" "$LAST_LAN_FP_IP" "LAN_FP_IP"
if [ $? -eq 1 ]; then
ANY_IP_LIST_CHANGED=1
fi
# Check LAN_GM_IP
check_ip_list_changed "$LAN_GM_IP" "$LAST_LAN_GM_IP" "LAN_GM_IP"
if [ $? -eq 1 ]; then
ANY_IP_LIST_CHANGED=1
fi
# Check for changes in the existence of the server XHTTP address file.
if [ "$XHTTP_FILE_EXISTS" != "$LAST_XHTTP_FILE_EXISTS" ]; then
loger 6 "xhttp address file existence changed: '$LAST_XHTTP_FILE_EXISTS' -> '$XHTTP_FILE_EXISTS'"
ANY_IP_LIST_CHANGED=1
fi
# Check for XHTTP file content changes (compare hashes only when the file exists in both checks)
if [ "$XHTTP_FILE_EXISTS" = "1" ] && [ "$LAST_XHTTP_FILE_EXISTS" = "1" ]; then
if [ "$XHTTP_FILE_HASH" != "$LAST_XHTTP_FILE_HASH" ]; then
loger 6 "xhttp address file content changed (hash: '$LAST_XHTTP_FILE_HASH' -> '$XHTTP_FILE_HASH')"
ANY_IP_LIST_CHANGED=1
else
loger 6 "xhttp address file content unchanged"
fi
elif [ "$XHTTP_FILE_EXISTS" = "0" ] && [ "$LAST_XHTTP_FILE_EXISTS" = "1" ]; then
loger 6 "xhttp address file deleted"
ANY_IP_LIST_CHANGED=1
elif [ "$XHTTP_FILE_EXISTS" = "1" ] && [ "$LAST_XHTTP_FILE_EXISTS" = "0" ]; then
loger 6 "xhttp address file created"
ANY_IP_LIST_CHANGED=1
fi
# STEP 3: Determine if forced rebuild is needed
FORCE_RECREATE=0
PERSISTENCE_EXISTS=0
# Trigger conditions:
# 1. TPROXY changes from empty ↔ has value
# 2. PROXY_PORTS changes from empty ↔ has value
# 3. Any IP list has changed
if [ "$TPROXY_HAS_VALUE" != "$LAST_HAS_VALUE" ] || \
[ "$PROXY_HAS_VALUE" != "$LAST_PROXY_HAS_VALUE" ] || \
[ "$ANY_IP_LIST_CHANGED" = "1" ]; then
FORCE_RECREATE=1
loger 6 "TPROXY, PROXY_PORTS or any IP list changed → force rebuild rules"
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
else
# No FORCE_RECREATE triggered → check persistence file
if [ -f "$NFTABLES_RULES_FILE" ] && [ -s "$NFTABLES_RULES_FILE" ]; then
PERSISTENCE_EXISTS=1
loger 6 "Persistence file exists: $NFTABLES_RULES_FILE"
else
PERSISTENCE_EXISTS=0
loger 6 "Persistence file does not exist or empty"
fi
fi
# STEP 4: Save current state
echo "$TPROXY" > "$TPROXY_STATE_FILE"
echo "$PROXY_PORTS" > "$PROXY_PORTS_STATE_FILE"
echo "$(normalize_ip_list "$WAN_BP_IP")" > "$WAN_BP_IP_STATE_FILE"
echo "$(normalize_ip_list "$LAN_AC_IP")" > "$LAN_AC_IP_STATE_FILE"
echo "$(normalize_ip_list "$LAN_BP_IP")" > "$LAN_BP_IP_STATE_FILE"
echo "$(normalize_ip_list "$WAN_FW_IP")" > "$WAN_FW_IP_STATE_FILE"
echo "$(normalize_ip_list "$LAN_FP_IP")" > "$LAN_FP_IP_STATE_FILE"
echo "$(normalize_ip_list "$LAN_GM_IP")" > "$LAN_GM_IP_STATE_FILE"
echo "$XHTTP_FILE_EXISTS" > "$XHTTP_FILE_STATE_FILE"
echo "$XHTTP_FILE_HASH" > "$XHTTP_FILE_HASH_FILE"
# STEP 5: Check if run mode changed
if runmode_change "$RUNMODE"; then
MODE_CHANGED=1
loger 6 "Runmode changed: MODE_CHANGED=1"
else
MODE_CHANGED=0
loger 6 "Runmode unchanged: MODE_CHANGED=0"
fi
# STEP 6: Mode changed and persistence exists → delete once
if [ "$MODE_CHANGED" = "1" ] && [ "$PERSISTENCE_EXISTS" = "1" ]; then
loger 6 "Mode changed → removing persistence file"
rm -f "$NFTABLES_RULES_FILE"
PERSISTENCE_EXISTS=0
fi
# STEP 7: FORCE_RECREATE priority → must rebuild rules
if [ "$FORCE_RECREATE" = "1" ]; then
loger 5 "Forced regeneration of NFTables rules"
if flush_r && ipset_r && fw_rule && ac_rule && tp_rule && gen_include; then
loger 5 "NFT rules applied successfully (forced rebuild)"
persist_nftables_rules
[ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon
exit 0
else
loger 3 "NFT forced rebuild failed!"
exit 1
fi
fi
# STEP 8: Persistence exists → try restore
if [ "$PERSISTENCE_EXISTS" = "1" ]; then
# Restore rules
if restore_from_persistence; then
loger 5 "NFT rules restored from persistence"
gen_include
[ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon
exit 0
else
loger 3 "Restore failed → fallback to full setup"
PERSISTENCE_EXISTS=0
fi
fi
# STEP 9: Persistence doesn't exist or restore failed → generate new rules
if flush_r && ipset_r && fw_rule && ac_rule && tp_rule && gen_include; then
loger 5 "NFTables rules applied successfully"
persist_nftables_rules
[ "$ENABLE_AUTO_UPDATE" = "1" ] && start_auto_update_daemon
exit 0
else
loger 3 "NFTables setup failed!"
exit 1
fi
else
# iptables
if flush_r && fw_rule && ipset_r && ac_rule && tp_rule && gen_include; then
loger 5 "iptables rules applied successfully"
exit 0
else
loger 3 "iptables setup failed!"
exit 1
fi
fi
fi