Browse Source

Merge pull request #1822 from zxlhhyccc/tuic

luci-app-ssr-plus: Full fix nftables fw4 support.
zxl hhyccc 2 weeks ago
parent
commit
3ccd51150e

+ 228 - 111
luci-app-ssr-plus/root/usr/bin/ssr-rules

@@ -14,7 +14,7 @@ detect_firewall() {
 		! grep -q "fw3" /etc/init.d/firewall 2>/dev/null; then
 			USE_NFT=1  
 			NFT="nft"
-			FWI=$(uci get firewall.shadowsocksr.path 2>/dev/null) # firewall include file
+			FWI=$(uci get firewall.shadowsocksr.path 2>/dev/null)  # firewall include file
 	else
 		USE_NFT=0
 		IPT="iptables -t nat"                                 # alias of iptables
@@ -82,32 +82,59 @@ flush_r() {
 }
 
 flush_nftables() {
-	# Remove nftables rules and sets more carefully
-	$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
-	
-	# Clean up routing rules
+	# 删除 inet ss_spec 表
+	if $NFT list table inet ss_spec >/dev/null 2>&1; then
+		# 删除所有链
+		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
+
+		# 删除所有集合(set)
+		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
+
+		# 删除整个表
+		$NFT delete table inet ss_spec 2>/dev/null
+	fi
+
+	# 删除 ip ss_spec_mangle 表(如果存在)
+	if $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then
+		# 删除所有链
+		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
+
+		# 删除所有集合(set)
+		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
+
+		# 删除整个表
+		$NFT delete table ip ss_spec_mangle 2>/dev/null
+	fi
+
+	# 删除策略路由标记规则
 	ip rule del fwmark 0x01/0x01 table 100 2>/dev/null
 	ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
 
-	# 删除 nftables 集合
-	$NFT delete set inet ss_spec ss_spec_lan_ac 2>/dev/null
-	$NFT delete set inet ss_spec ss_spec_wan_ac 2>/dev/null
-	$NFT delete set inet ss_spec ssr_gen_router 2>/dev/null
-	$NFT delete set inet ss_spec fplan 2>/dev/null
-	$NFT delete set inet ss_spec bplan 2>/dev/null
-	$NFT delete set inet ss_spec gmlan 2>/dev/null
-	$NFT delete set inet ss_spec oversea 2>/dev/null
-	$NFT delete set inet ss_spec whitelist 2>/dev/null
-	$NFT delete set inet ss_spec blacklist 2>/dev/null
-	$NFT delete set inet ss_spec netflix 2>/dev/null
-	$NFT delete set inet ss_spec gfwlist 2>/dev/null
-	$NFT delete set inet ss_spec china 2>/dev/null
-	$NFT delete set inet ss_spec music 2>/dev/null
+	# 可选:强制删除所有 ss_spec 相关的集合(即使表被误删)
+	for setname in ss_spec_lan_ac ss_spec_wan_ac ssr_gen_router fplan bplan gmlan oversea whitelist blacklist netflix gfwlist china music; do
+		$NFT delete set inet ss_spec $setname 2>/dev/null
+		$NFT delete set ip ss_spec_mangle $setname 2>/dev/null
+	done
 
+	# 重置防火墙 include 文件
 	[ -n "$FWI" ] && echo '#!/bin/sh' >"$FWI"
-	
+
 	return 0
 }
 
@@ -148,13 +175,13 @@ ipset_r() {
 }
 
 ipset_nft() {
-	[ -f "$IGNORE_LIST" ] && /usr/share/shadowsocksr/chinaipset.sh "$IGNORE_LIST"
-
 	# Create nftables table and sets
-	$NFT list table inet ss_spec >/dev/null 2>&1 || $NFT add table inet ss_spec
+	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 ss_spec_wan_ac gmlan fplan bplan whitelist blacklist netflix; do
+	for setname in ss_spec_wan_ac china gmlan fplan bplan whitelist blacklist netflix; 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
@@ -162,6 +189,11 @@ ipset_nft() {
 		fi
     done
 
+	# 批量导入中国IP列表
+	if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then
+		$NFT add element inet ss_spec china { $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') } 2>/dev/null
+	fi
+
     # 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
@@ -181,7 +213,7 @@ ipset_nft() {
 
     # Create main chain for WAN access control
     if ! $NFT list chain inet ss_spec ss_spec_wan_ac >/dev/null 2>&1; then
-		$NFT add chain inet ss_spec ss_spec_wan_ac '{ type nat hook prerouting priority dstnat - 1; policy accept; }' 2>/dev/null
+		$NFT add chain inet ss_spec ss_spec_wan_ac 2>/dev/null
     fi
     $NFT flush chain inet ss_spec ss_spec_wan_ac 2>/dev/null
 
@@ -197,9 +229,7 @@ ipset_nft() {
     
     # Add basic rules
     $NFT add rule inet ss_spec ss_spec_wan_ac tcp dport 53 ip daddr 127.0.0.0/8 return
-    $NFT add rule inet ss_spec ss_spec_wan_ac udp dport 53 ip daddr 127.0.0.0/8 return
     $NFT add rule inet ss_spec ss_spec_wan_ac tcp dport != 53 ip daddr "$server" return
-    $NFT add rule inet ss_spec ss_spec_wan_ac udp dport != 53 ip daddr "$server" return
 
     # Add special IP ranges to WAN AC set
     for ip in $(gen_spec_iplist); do
@@ -217,7 +247,9 @@ ipset_nft() {
 		fi
 		;;
     gfw)
-		$NFT add set inet ss_spec gfwlist '{ type ipv4_addr; flags interval; }' 2>/dev/null
+		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 2>/dev/null
 		$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @gfwlist jump ss_spec_wan_fw 2>/dev/null
 		if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
@@ -225,7 +257,9 @@ ipset_nft() {
 		fi
 		;;
     oversea)
-		$NFT add set inet ss_spec oversea '{ type ipv4_addr; flags interval; }' 2>/dev/null
+		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
 		if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
 			$NFT insert rule inet ss_spec ss_spec_wan_ac ip daddr @oversea jump SS_SPEC_WAN_FW 2>/dev/null
 			$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan jump ss_spec_wan_fw 2>/dev/null
@@ -309,10 +343,10 @@ ipset_iptables() {
 	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
+		$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
+		$IPT -I SS_SPEC_WAN_AC -m set --match-set bplan src -j RETURN
 	ipset -N whitelist hash:net 2>/dev/null
 	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
@@ -354,31 +388,24 @@ fw_rule() {
 
 fw_rule_nft() {
 	# Exclude special local addresses
-	$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 0.0.0.0/8 return
-	$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 10.0.0.0/8 return
-	$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 127.0.0.0/8 return
-	$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 169.254.0.0/16 return
-	$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 172.16.0.0/12 return
-	$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 192.168.0.0/16 return
-	$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 224.0.0.0/4 return
-	$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr 240.0.0.0/4 return
+	if $NFT list chain inet ss_spec ss_spec_wan_fw >/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_wan_fw ip daddr $net return 2>/dev/null
+		done
+	fi
 
 	# redirect/translation: when PROXY_PORTS present, redirect those tcp ports to local_port
 	if [ -n "$PROXY_PORTS" ]; then
 		PORTS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
-		if ! $NFT list chain inet ss_spec ss_spec_wan_fw 2>/dev/null | grep -q "tcp dport { $PORTS } redirect to :$local_port"; then
-			if ! $NFT add rule inet ss_spec ss_spec_wan_fw tcp dport { $PORTS } redirect to :"$local_port" 2>/dev/null; then
-				loger 3 "Can't redirect, please check nftables."
-				return 1
-			fi
-		fi
+		RULE="tcp dport { $PORTS } redirect to :$local_port"
 	else
 		# default: redirect everything except ssh(22)
-		if ! $NFT list chain inet ss_spec ss_spec_wan_fw 2>/dev/null | grep -q "tcp dport != 22 redirect to :$local_port"; then
-			if ! $NFT add rule inet ss_spec ss_spec_wan_fw tcp dport != 22 redirect to :$local_port 2>/dev/null; then
-				loger 3 "Can't redirect, please check nftables."
-				return 1
-			fi
+		RULE="tcp dport != 22 redirect to :$local_port"
+	fi
+	if ! $NFT list chain inet ss_spec ss_spec_wan_fw 2>/dev/null | grep -q "$RULE"; then
+		if ! $NFT add rule inet ss_spec ss_spec_wan_fw $RULE 2>/dev/null; then
+			loger 3 "Can't redirect, please check nftables."
+			return 1
 		fi
 	fi
 
@@ -416,7 +443,12 @@ ac_rule_nft() {
 
 	if [ -n "$LAN_AC_IP" ]; then
 		# Create LAN access control set if needed
-		$NFT add set inet ss_spec ss_spec_lan_ac '{ type ipv4_addr; flags interval; }' 2>/dev/null
+		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
@@ -436,25 +468,29 @@ ac_rule_nft() {
 	fi
 
 	# 创建ss_spec_prerouting链
-	if ! $NFT list chain inet ss_spec_prerouting >/dev/null 2>&1; then
-		$NFT add chain inet ss_spec ss_spec_prerouting '{ type filter hook prerouting priority -1; policy accept; }'
+	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 -150; policy accept; }'
 	fi
 	$NFT flush chain inet ss_spec ss_spec_prerouting 2>/dev/null
 
 	# 创建ss_spec_output链
 	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 -1; policy accept; }'
+		$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority -100; policy accept; }'
 	fi
 	$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
 
 	# Build a rule in the prerouting hook chain that jumps to business chain with conditions
+	if [ -n "$PROXY_PORTS" ]; then
+		EXT_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
+	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 insert rule inet ss_spec ss_spec_prerouting tcp dport $EXT_ARGS $MATCH_SET comment "\"$TAG\"" jump ss_spec_wan_ac 2>/dev/null
+			$NFT insert rule inet ss_spec ss_spec_prerouting meta l4proto tcp th dport { $EXT_ARGS } $MATCH_SET jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
 		else
-			$NFT insert rule inet ss_spec ss_spec_prerouting tcp dport $EXT_ARGS comment "\"$TAG\"" jump ss_spec_wan_ac
+			$NFT insert rule inet ss_spec ss_spec_prerouting meta l4proto tcp th dport { $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
@@ -463,9 +499,9 @@ ac_rule_nft() {
 			[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
 			if [ -n "$IFNAME" ]; then
 				if [ -n "$MATCH_SET" ]; then
-					$NFT insert rule inet ss_spec ss_spec_prerouting iifname "$IFNAME" tcp dport $EXT_ARGS $MATCH_SET comment "\"$TAG\"" jump ss_spec_wan_ac 2>/dev/null
+					$NFT insert rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" meta l4proto tcp th dport { $EXT_ARGS } $MATCH_SET jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
 				else
-					$NFT insert rule inet ss_spec ss_spec_prerouting iifname "$IFNAME" tcp dport $EXT_ARGS comment "\"$TAG\"" jump ss_spec_wan_ac 2>/dev/null
+					$NFT insert rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" meta l4proto tcp th dport { $EXT_ARGS } jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
 				fi
 			fi
 		done
@@ -474,7 +510,7 @@ ac_rule_nft() {
 	case "$OUTPUT" in
 	1)
 		# create output hook chain & route output traffic into router chain
-		$NFT add rule inet ss_spec ss_spec_output tcp dport $EXT_ARGS comment "\"$TAG\"" jump ss_spec_wan_ac 2>/dev/null
+		$NFT insert rule inet ss_spec ss_spec_output meta l4proto tcp th dport { $EXT_ARGS } jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
 		;;
 	2)
 		# router mode output chain: create ssr_gen_router set & router chain
@@ -485,7 +521,7 @@ ac_rule_nft() {
 		$NFT add 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 dport $EXT_ARGS comment "\"$TAG\"" jump ss_spec_router 2>/dev/null
+		$NFT add rule inet ss_spec ss_spec_output meta l4proto tcp th dport { $EXT_ARGS } jump ss_spec_router comment "\"$TAG\"" 2>/dev/null
 		;;
 	esac
 	return 0
@@ -549,89 +585,170 @@ tp_rule() {
 }
 
 tp_rule_nft() {
-	[ -n "$TPROXY" ] || return 0
-
 	# set up routing table for tproxy
 	ip rule add fwmark 0x01/0x01 table 100 2>/dev/null
 	ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null
 
 	# create mangle table and tproxy chain
-	$NFT add table ip ss_spec_mangle 2>/dev/null
+	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=""
+	local EXT_ARGS=""
+
+	if [ -n "$PROXY_PORTS" ]; then
+		EXT_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
+	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 ss_spec_wan_ac 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
+
+	# 批量导入中国IP列表
+	if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then
+		$NFT add element ip ss_spec_mangle china { $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') } 2>/dev/null
+	fi
+
 	# use priority mangle for compatibility with other rules
-	$NFT add chain ip ss_spec_mangle ss_spec_tproxy '{ type filter hook prerouting priority mangle; }' 2>/dev/null
+	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
 
 	# basic return rules in tproxy chain
 	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 53 return 2>/dev/null
-	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 0.0.0.0/8 return 2>/dev/null
-	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 10.0.0.0/8 return 2>/dev/null
-	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 127.0.0.0/8 return 2>/dev/null
-	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 169.254.0.0/16 return 2>/dev/null
-	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 172.16.0.0/12 return 2>/dev/null
-	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 192.168.0.0/16 return 2>/dev/null
-	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 224.0.0.0/4 return 2>/dev/null
-	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr 240.0.0.0/4 return 2>/dev/null
+
+	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
 
 	# avoid redirecting to udp server address
-	$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport != 53 ip daddr "$server" return 2>/dev/null
+	if [ -n "$server" ]; then
+		$NFT add rule ip ss_spec_mangle ss_spec_tproxy 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 [ "$server" != "$SERVER" ]; then
-		$NFT add element inet ss_spec whitelist "{ $SERVER }" 2>/dev/null
+	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 udp ip saddr @bplan return 2>/dev/null
-	if [ -n "$PROXY_PORTS" ]; then
-		PORTS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
-		$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $PORTS } ip saddr @fplan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+	$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @bplan return 2>/dev/null
+
+	if [ -n "$EXT_ARGS" ]; then
+		$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip saddr @fplan tproxy to :"$LOCAL_PORT" meta mark set 0x01
 	else
-		$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @fplan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+		$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @fplan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
 	fi
-	
+
 	# Handle different run modes for nftables
 	case "$RUNMODE" in
 	router)
-		$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr @ss_spec_wan_ac return 2>/dev/null
-		$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr @china return 2>/dev/null
+		$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 udp dport 80 drop 2>/dev/null
-		$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @gmlan ip daddr != @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
-		if [ -n "$PROXY_PORTS" ]; then
-			PORTS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
-			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $PORTS } ip daddr != @ss_spec_wan_ac tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+		$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+		if [ -n "$EXT_ARGS" ]; then
+			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip daddr != @ss_spec_wan_ac tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
 		else
-			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr != @ss_spec_wan_ac tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+			$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr != @ss_spec_wan_ac tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
 		fi
 		;;
 	gfw)
-		$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip daddr @china 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 udp dport 80 drop 2>/dev/null
-		if [ -n "$PROXY_PORTS" ]; then
-			PORTS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
-			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $PORTS } ip daddr @gfwlist tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+		if [ -n "$EXT_ARGS" ]; then
+			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip daddr @gfwlist tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
 		fi
-		$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp ip saddr @gmlan ip daddr != @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+		$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
 		;;
 	oversea)
-		if [ -n "$PROXY_PORTS" ]; then
-			PORTS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
-			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $PORTS } ip saddr @oversea tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
-			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $PORTS } ip daddr @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+		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 udp ip saddr @gmlan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+		if ! $NFT list set ip ss_spec_mangle china >/dev/null 2>&1; then
+			$NFT add set ip ss_spec_mangle china '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
+		fi
+		if [ -n "$EXT_ARGS" ]; then
+			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip saddr @oversea tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip daddr @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+		fi
+		$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
 		;;
 	all)
-		if [ -n "$PROXY_PORTS" ]; then
-			PORTS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
-			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $PORTS } tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
+		if [ -n "$EXT_ARGS" ]; then
+			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
 		else
 			$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
 		fi
 		;;
 	esac
 
-	# insert jump from ip prerouting to our tproxy chain
-	PORTS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
-	$NFT add rule ip ss_spec_mangle prerouting udp dport { $PORTS } comment "\"$TAG\"" jump ss_spec_tproxy 2>/dev/null
+	# 创建 prerouting 链(hook prerouting)
+	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
+
+	# 添加规则到 prerouting 链
+	if [ -z "$Interface" ]; then
+		# 全局规则
+		if [ -n "$MATCH_SET" ]; then
+			$NFT add rule ip ss_spec_mangle prerouting udp dport { $EXT_ARGS } $MATCH_SET jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
+		else
+			$NFT add rule ip ss_spec_mangle prerouting udp dport { $EXT_ARGS } jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
+		fi
+	else
+		# 指定接口
+		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" udp dport { $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" udp dport { $EXT_ARGS } jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
+				fi
+			fi
+		done
+	fi
 
 	return $?
 }
@@ -734,12 +851,12 @@ gen_include_nft() {
 	[ -n "$FWI" ] && echo '#!/bin/sh' >"$FWI"
 	cat <<-'EOF' >>"$FWI"
 		# Clear existing ss_spec tables
-		nft add table inet ss_spec 2>/dev/null
-		nft add table ip ss_spec 2>/dev/null
-		nft add table ip ss_spec_mangle 2>/dev/null
+		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
-		nft list ruleset | awk '/table (inet|ip) ss_spec/{flag=1} flag'
+		nft list ruleset | awk '/^table (inet|ip) ss_spec/{flag=1} /^table / && !/^table (inet|ip) ss_spec/{flag=0} flag'
 	EOF
 	chmod +x "$FWI"
 }
@@ -867,7 +984,7 @@ case "$TPROXY" in
 	;;
 esac
 
-# 首先检查nftables是否正常工作
+# First check whether nftables is working properly
 if [ "$USE_NFT" = "1" ]; then
 	if ! $NFT list tables 2>/dev/null; then
 		loger 3 "nftables is not working properly, check if nftables is installed and running"

+ 4 - 19
luci-app-ssr-plus/root/usr/share/shadowsocksr/chinaipset.sh

@@ -1,22 +1,7 @@
 #!/bin/sh
 [ -f "$1" ] && china_ip=$1
-
-if command -v nft >/dev/null 2>&1; then
-    # 确保表和集合存在
-    nft add table inet ss_spec 2>/dev/null
-    nft add set inet ss_spec china '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
-    nft flush set inet ss_spec china 2>/dev/null
-
-    # 批量导入
-    if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then
-        echo "批量导入中国IP列表..."
-        nft add element inet ss_spec china { $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') } 2>/dev/null
-        echo "中国IP集合导入完成"
-    fi
-else
-    ipset -! flush china 2>/dev/null
-    ipset -! -R <<-EOF || exit 1
-        create china hash:net
-        $(cat ${china_ip:=/etc/ssrplus/china_ssr.txt} | sed -e "s/^/add china /")
+ipset -! flush china 2>/dev/null
+ipset -! -R <<-EOF || exit 1
+	create china hash:net
+	$(cat ${china_ip:=/etc/ssrplus/china_ssr.txt} | sed -e "s/^/add china /")
 EOF
-fi

+ 11 - 7
luci-app-ssr-plus/root/usr/share/shadowsocksr/gfw2ipset.sh

@@ -2,14 +2,18 @@
 
 . $IPKG_INSTROOT/etc/init.d/shadowsocksr
 
+if command -v nft >/dev/null 2>&1; then
+    nft_support=1
+fi
+
 netflix() {
 	if [ -f "$TMP_DNSMASQ_PATH/gfw_list.conf" ]; then
 		for line in $(cat /etc/ssrplus/netflix.list); do sed -i "/$line/d" $TMP_DNSMASQ_PATH/gfw_list.conf; done
 		for line in $(cat /etc/ssrplus/netflix.list); do sed -i "/$line/d" $TMP_DNSMASQ_PATH/gfw_base.conf; done
 	fi
-	if command -v nft >/dev/null 2>&1; then
+	if [ "$nft_support" = "1" ]; then
 		# 移除 ipset
-		cat /etc/ssrplus/netflix.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$1/" >$TMP_DNSMASQ_PATH/netflix_forward.conf
+		cat /etc/ssrplus/netflix.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$1\nnftset=\/&\/4#inet#ss_spec#netflix/" >$TMP_DNSMASQ_PATH/netflix_forward.conf
 	else
 		cat /etc/ssrplus/netflix.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$1\nipset=\/&\/netflix/" >$TMP_DNSMASQ_PATH/netflix_forward.conf
 	fi
@@ -22,11 +26,11 @@ else
 	cp -rf /etc/ssrplus/gfw_base.conf $TMP_DNSMASQ_PATH/
 fi
 
-if command -v nft >/dev/null 2>&1; then
+if [ "$nft_support" = "1" ]; then
     # 移除 ipset 指令
     for conf_file in gfw_base.conf gfw_list.conf; do
         if [ -f "$TMP_DNSMASQ_PATH/$conf_file" ]; then
-            sed -i '/ipset=/d' "$TMP_DNSMASQ_PATH/$conf_file"
+            sed -i 's|ipset=/\([^/]*\)/\([^[:space:]]*\)|nftset=/\1/4#inet#ss_spec#\2|g' "$TMP_DNSMASQ_PATH/$conf_file"
         fi
     done
 fi
@@ -59,9 +63,9 @@ while read line; do sed -i "/$line/d" $TMP_DNSMASQ_PATH/gfw_list.conf; done < /e
 while read line; do sed -i "/$line/d" $TMP_DNSMASQ_PATH/gfw_base.conf; done < /etc/ssrplus/deny.list
 
 # 此处直接使用 cat 因为有 sed '/#/d' 删除了 数据
-if command -v nft >/dev/null 2>&1; then
-	cat /etc/ssrplus/black.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$dns_port/" >$TMP_DNSMASQ_PATH/blacklist_forward.conf
-	cat /etc/ssrplus/white.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1/" >$TMP_DNSMASQ_PATH/whitelist_forward.conf
+if [ "$nft_support" = "1" ]; then
+	cat /etc/ssrplus/black.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$dns_port\nnftset=\/&\/4#inet#ss_spec#blacklist/" >$TMP_DNSMASQ_PATH/blacklist_forward.conf
+	cat /etc/ssrplus/white.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1\nnftset=\/&\/4#inet#ss_spec#whitelist/" >$TMP_DNSMASQ_PATH/whitelist_forward.conf
 else
 	cat /etc/ssrplus/black.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1#$dns_port\nipset=\/&\/blacklist/" >$TMP_DNSMASQ_PATH/blacklist_forward.conf
 	cat /etc/ssrplus/white.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/server=\/&\/127.0.0.1\nipset=\/&\/whitelist/" >$TMP_DNSMASQ_PATH/whitelist_forward.conf