#!/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
# 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 <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
	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 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
	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 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 "$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

	# 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() {
	# 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() {
	# 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".device 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".device 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
			# 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".device 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".device 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 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
		;;
	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 ${FWMARK} 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 ${FWMARK} 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 ${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".device 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
	$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 ${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}
		;;
	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 ${FWMARK}
		$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark ${FWMARK}
		$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -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".device 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: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
