#!/bin/sh # # Copyright (C) 2017 openwrt-ssr # Copyright (C) 2017 yushi studio # # 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 # ASCII code for SSRP.Use whatever,just not the same. FWMARK="0x53535250" 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 ip address of shadowsocksr remote server -l port number of shadowsocksr local server -S ip address of shadowsocksr remote UDP server -L port number of shadowsocksr local UDP server -i a file content is bypassed ip list -a lan ip of access control, need a prefix to define access control mode -b wan ip of will be bypassed -w wan ip of will be forwarded -B lan ip of will be bypassed proxy -p lan ip of will be global proxy -G lan ip of will be game mode proxy -D proxy ports -F shunt mode -N shunt server IP -M shunt proxy mode -m Interface name -I a file content is bypassed shunt ip list -e 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 -y disable all auxiliary UDP rules when UDP transparent proxy is unavailable -f flush the rules -g gfwlist mode -r router 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 if ip rule show | grep -Eq "fwmark ${FWMARK}.*lookup 999"; then ip rule del fwmark ${FWMARK} table 999 2>/dev/null fi if ip route show table 999 | grep -Eq "^local.*dev lo"; then ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null fi # 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 whitelist blacklist 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 if ip rule show | grep -Eq "fwmark ${FWMARK}.*lookup 999"; then ip rule del fwmark ${FWMARK} table 999 2>/dev/null fi if ip route show table 999 | grep -Eq "^local.*dev lo"; then ip route del local 0.0.0.0/0 dev lo table 999 2>/dev/null fi for setname in ss_spec_lan_ac ss_spec_wan_ac ssr_gen_router \ china fplan bplan gmlan whitelist blacklist 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 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 "$IGNORE_LIST" ]; then SKIP_INET=1 /usr/share/shadowsocksr/chinaipset.sh "$IGNORE_LIST" 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 # 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 ;; 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 -m mark --mark 255 -j RETURN $IPT -I SS_SPEC_WAN_AC -p tcp --dport 53 -d 127.0.0.0/8 -j RETURN [ -n "$server" ] && $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 ;; 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 return $? } fw_rule() { if [ "$USE_NFT" = "1" ]; then fw_rule_nft else fw_rule_iptables fi return $? } fw_rule_nft() { # 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 return $? } fw_rule_iptables() { # 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" ] && [ "$DISABLE_UDP_RULES" != "1" ]; 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 $NFT add rule inet ss_spec ss_spec_output meta mark 255 return 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 $NFT add rule inet ss_spec ss_spec_output meta mark 255 return 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 meta mark 255 return 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" ] && [ "$DISABLE_UDP_RULES" != "1" ]; then # Add UDP 443 block rule if [ -z "$Interface" ]; then # Global rules if [ -n "$MATCH_SET" ]; then $ipt -I PREROUTING 1 -p udp $EXT_ARGS $MATCH_SET --dport 443 -j DROP -m comment --comment "$TAG" else $ipt -I PREROUTING 1 -p udp $EXT_ARGS --dport 443 -j DROP -m comment --comment "$TAG" 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 $ipt -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p udp $EXT_ARGS $MATCH_SET --dport 443 -j DROP -m comment --comment "$TAG" else $ipt -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p udp $EXT_ARGS --dport 443 -j DROP -m comment --comment "$TAG" fi fi done fi fi if [ -z "$Interface" ]; then # Global rules if [ -n "$MATCH_SET" ]; then $IPT -I PREROUTING 1 -p tcp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_WAN_AC else $IPT -I PREROUTING 1 -p tcp $EXT_ARGS -m comment --comment "$TAG" -j SS_SPEC_WAN_AC 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 $IPT -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p tcp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_WAN_AC else $IPT -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p tcp $EXT_ARGS -m comment --comment "$TAG" -j SS_SPEC_WAN_AC fi 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 mark --mark 255 -j RETURN && \ $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 ${FWMARK}.*lookup 999"; then ip rule add fwmark ${FWMARK} table 999 priority 999 2>/dev/null fi if ! ip route show table 999 | grep -Eq "^local.*dev lo"; then ip route add local 0.0.0.0/0 dev lo table 999 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 "$IGNORE_LIST" ]; then SKIP_INET=2 /usr/share/shadowsocksr/chinaipset.sh "$IGNORE_LIST" 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 ${FWMARK} 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 ${FWMARK} 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 ${FWMARK} 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 ${FWMARK} 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 ${FWMARK} 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 ${FWMARK} 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 ${FWMARK}.*lookup 999"; then ip rule add fwmark ${FWMARK} table 999 priority 999 2>/dev/null fi if ! ip route show table 999 | grep -Eq "^local.*dev lo"; then ip route add local 0.0.0.0/0 dev lo table 999 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="-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 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 [ -n "$SERVER" ] && $ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN [ -n "$SERVER" ] && [ "$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 ${FWMARK} 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 ${FWMARK} $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 ${FWMARK} ;; 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 ${FWMARK} $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 ${FWMARK} ;; all) $ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK} ;; esac if [ -z "$Interface" ]; then # Global rules if [ -n "$MATCH_SET" ]; then $ipt -I PREROUTING 1 -p udp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_TPROXY else $ipt -I PREROUTING 1 -p udp $EXT_ARGS -m comment --comment "$TAG" -j SS_SPEC_TPROXY 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 $ipt -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p udp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_TPROXY else $ipt -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p udp $EXT_ARGS -m comment --comment "$TAG" -j SS_SPEC_TPROXY fi fi 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 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 "$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 "$rules_file" return 1 # Need update fi rm -f "$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 # 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 # Otherwise, if persistence file exists, compare rules elif [ -f "$NFTABLES_RULES_FILE" ]; then if compare_rules; then loger 6 "Rules unchanged, skipping persistence update" return 0 else loger 6 "Rules changed, updating persistence" 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 "$AUTO_UPDATE_INTERVAL" if [ -x "/usr/bin/ssr-rules" ]; then # -C returns 0 if rules are OK, non-zero if need update if /usr/bin/ssr-rules -C >/dev/null 2>&1; then logger -t ssr-rules[daemon] "Rules status OK, no update needed" else 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 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:oOuUyfgrzAKPCRXh" 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 ;; y) DISABLE_UDP_RULES=1 ;; g) RUNMODE=gfw ;; r) RUNMODE=router ;; 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 "$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) : ${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