Files
openwrt_packages/luci-app-ssr-plus/root/usr/bin/ssr-rules
2025-12-08 00:10:24 +08:00

1729 lines
57 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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.
#
# Detect firewall version and set appropriate tools
detect_firewall() {
if command -v nft >/dev/null 2>&1 && \
{ [ -n "$(uci get firewall.@defaults[0].syn_flood 2>/dev/null)" ] || \
[ -n "$(uci get firewall.@defaults[0].synflood_protect 2>/dev/null)" ]; } && \
! 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
else
USE_NFT=0
IPT="iptables -t nat" # alias of iptables
FWI=$(uci get firewall.shadowsocksr.path 2>/dev/null) # firewall include file
fi
}
# Initialize firewall detection
detect_firewall
TAG="_SS_SPEC_RULE_" # comment tag
# 这些变量将在后续的 getopts 参数解析中被赋值
ENABLE_AUTO_UPDATE=0
STOP_AUTO_UPDATE=0
FORCE_UPDATE=0
CHECK_STATUS=0
RESTORE_RULES=0
FLUSH_RULES=0
CLEANUP_PERSISTENCE=0
if [ "$USE_NFT" = "1" ]; then
# NFTables persistence directory
NFTABLES_RULES_DIR="/usr/share/nftables.d/ruleset-post"
NFTABLES_RULES_FILE="$NFTABLES_RULES_DIR/99-shadowsocksr.nft"
# Auto-update configuration
AUTO_UPDATE_INTERVAL=300 # 自动更新检查间隔0表示禁用自动更新
fi
# 修改 usage 函数
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
# 新增持久化管理选项 (使用不同的字母避免冲突)
-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
}
# 清理持久化和运行模块文件等
cleanup_persistence_files() {
if [ "$USE_NFT" != "1" ]; then
return 0
fi
# 删除持久化规则文件
if [ -f "$NFTABLES_RULES_FILE" ]; then
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
loger 5 "Removed persistence file: $NFTABLES_RULES_FILE"
fi
# 删除运行模块文件
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
# 删除 TPROXY 文件
if [ -f "/tmp/.last_tproxy" ]; then
rm -f "/tmp/.last_tproxy" 2>/dev/null
loger 5 "Removed run mode file: /tmp/.last_tproxy"
fi
# 删除 PROXY_PORTS 文件
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
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() {
# 删除 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
# 可选:强制删除所有 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"
# 清理持久化和运行模块文件
if [ "$CLEANUP_PERSISTENCE" = "1" ]; then
cleanup_persistence_files
fi
loger 6 "Memory rules flushed successfully"
return 0
}
flush_iptables_legacy() {
flush_iptables() {
local ipt="iptables -t $1"
local DAT=$(iptables-save -t $1)
eval $(echo "$DAT" | grep "$TAG" | sed -e 's/^-A/$ipt -D/' -e 's/$/;/')
for chain in $(echo "$DAT" | awk '/^:SS_SPEC/{print $1}'); do
$ipt -F ${chain:1} 2>/dev/null && $ipt -X ${chain:1}
done
}
flush_iptables nat
flush_iptables mangle
ip rule del fwmark 0x01/0x01 table 100 2>/dev/null
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
ipset -X ss_spec_lan_ac 2>/dev/null
ipset -X ss_spec_wan_ac 2>/dev/null
ipset -X ssr_gen_router 2>/dev/null
ipset -X fplan 2>/dev/null
ipset -X bplan 2>/dev/null
ipset -X gmlan 2>/dev/null
ipset -X oversea 2>/dev/null
ipset -X whitelist 2>/dev/null
ipset -X blacklist 2>/dev/null
ipset -X netflix 2>/dev/null
[ -n "$FWI" ] && echo '#!/bin/sh' >$FWI
return 0
}
ipset_r() {
if [ "$USE_NFT" = "1" ]; then
ipset_nft
else
ipset_iptables
fi
return $?
}
ipset_nft() {
# Create nftables table and sets
if ! $NFT list table inet ss_spec >/dev/null 2>&1; then
$NFT add table inet ss_spec 2>/dev/null
fi
# Create necessary collections
for setname in china gmlan fplan bplan whitelist blacklist netflix music; do
if ! $NFT list set inet ss_spec $setname >/dev/null 2>&1; then
$NFT add set inet ss_spec $setname '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
else
$NFT flush set inet ss_spec $setname 2>/dev/null
fi
done
# Bulk import china ip list safely (avoid huge single element limitation)
if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then
$NFT add element inet ss_spec china "{ $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') }" 2>/dev/null
fi
# 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_tcp ss_spec_wan_fw_udp ss_spec_wan_ac_tcp ss_spec_wan_ac_udp; 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_tcp 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_tcp 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_tcp ip daddr @blacklist jump ss_spec_wan_fw_tcp
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @whitelist return
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @fplan jump ss_spec_wan_fw_tcp
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @bplan return
# BASIC RULES (exceptions first) — UDP
$NFT add rule inet ss_spec ss_spec_wan_ac_udp meta l4proto udp udp dport 53 ip daddr 127.0.0.0/8 return
[ -n "$server" ] && $NFT add rule inet ss_spec ss_spec_wan_ac_udp meta l4proto udp udp dport != 53 ip daddr "$server" return
# Access control: blacklist -> whitelist -> fplan/bplan — UDP
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @blacklist jump ss_spec_wan_fw_udp
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @whitelist return
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip saddr @fplan jump ss_spec_wan_fw_udp
$NFT add rule inet ss_spec ss_spec_wan_ac_udp 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_tcp meta l4proto tcp ip daddr @music return
$NFT add rule inet ss_spec ss_spec_wan_ac_udp meta l4proto udp 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_tcp >/dev/null 2>&1; then
$NFT add set inet ss_spec ss_spec_wan_ac_tcp '{ type ipv4_addr; flags interval; auto-merge; }'
else
$NFT flush set inet ss_spec ss_spec_wan_ac_tcp 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_tcp "{ $ip }" 2>/dev/null
done
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @ss_spec_wan_ac_tcp return
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @china return
if $NFT list chain inet ss_spec ss_spec_wan_ac_tcp >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw_tcp
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp jump ss_spec_wan_fw_tcp
fi
if ! $NFT list set inet ss_spec ss_spec_wan_ac_udp >/dev/null 2>&1; then
$NFT add set inet ss_spec ss_spec_wan_ac_udp '{ type ipv4_addr; flags interval; auto-merge; }'
else
$NFT flush set inet ss_spec ss_spec_wan_ac_udp 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_udp "{ $ip }" 2>/dev/null
done
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @ss_spec_wan_ac_udp return
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @china return
if $NFT list chain inet ss_spec ss_spec_wan_fw_udp >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw_udp
$NFT add rule inet ss_spec ss_spec_wan_ac_udp jump ss_spec_wan_fw_udp
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_tcp ip daddr @china return
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @gfwlist jump ss_spec_wan_fw_tcp
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw_tcp
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @china return
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @gfwlist jump ss_spec_wan_fw_udp
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw_udp
;;
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_tcp ip daddr @oversea jump ss_spec_wan_fw_tcp
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip saddr @gmlan jump ss_spec_wan_fw_tcp
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp ip daddr @china jump ss_spec_wan_fw_tcp
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @oversea jump ss_spec_wan_fw_udp
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip saddr @gmlan jump ss_spec_wan_fw_udp
$NFT add rule inet ss_spec ss_spec_wan_ac_udp ip daddr @china jump ss_spec_wan_fw_udp
;;
all)
if $NFT list chain inet ss_spec ss_spec_wan_fw_tcp >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp jump ss_spec_wan_fw_tcp
fi
if $NFT list chain inet ss_spec ss_spec_wan_fw_udp >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac_udp jump ss_spec_wan_fw_udp
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 -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
ipset -N blacklist hash:net 2>/dev/null
$IPT -I SS_SPEC_WAN_AC -m set --match-set blacklist dst -j SS_SPEC_WAN_FW
$IPT -I SS_SPEC_WAN_AC -m set --match-set whitelist dst -j RETURN
if [ $(ipset list music -name -quiet | grep music) ]; then
$IPT -I SS_SPEC_WAN_AC -m set --match-set music dst -j RETURN 2>/dev/null
fi
for ip in $WAN_BP_IP; do ipset -! add whitelist "$ip"; done
for ip in $WAN_FW_IP; do ipset -! add blacklist "$ip"; done
if [ "$SHUNT_PORT" != "0" ]; then
ipset -N netflix hash:net 2>/dev/null
for ip in $(cat "${SHUNT_LIST:=/dev/null}" 2>/dev/null); do ipset -! add netflix "$ip"; done
case "$SHUNT_PORT" in
0) ;;
1)
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$local_port"
;;
*)
$IPT -I SS_SPEC_WAN_AC -p tcp -m set --match-set netflix dst -j REDIRECT --to-ports "$SHUNT_PORT"
if [ "$SHUNT_PROXY" = "1" ]; then
$IPT -I SS_SPEC_WAN_AC -p tcp -d "$SHUNT_IP" -j REDIRECT --to-ports "$local_port"
else
ipset -! add whitelist "$SHUNT_IP"
fi
;;
esac
fi
return $?
}
fw_rule() {
if [ "$USE_NFT" = "1" ]; then
fw_rule_nft
else
fw_rule_iptables
fi
return $?
}
fw_rule_nft() {
# set up routing table for tproxy
if ! ip rule show | grep -Eq "fwmark 0x0*1.*lookup 100"; then
ip rule add fwmark 0x01/0x01 table 100 2>/dev/null
fi
if ! ip route show table 100 | grep -q "^local.*dev lo"; then
ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null
fi
# redirect/translation: when PROXY_PORTS present, redirect those tcp ports to local_port
if [ -n "$PROXY_PORTS" ]; then
PORTS_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
if [ -n "$PORTS_ARGS" ]; then
TCP_EXT_ARGS="meta l4proto tcp tcp dport { $PORTS_ARGS }"
UDP_EXT_ARGS="meta l4proto udp udp dport { $PORTS_ARGS }"
TCP_RULE="meta l4proto tcp tcp dport { $PORTS_ARGS } counter redirect to :$local_port"
UDP_RULE="meta l4proto udp udp dport { $PORTS_ARGS } counter tproxy ip to :$local_port meta mark set 0x01"
fi
else
TCP_EXT_ARGS="meta l4proto tcp"
UDP_EXT_ARGS="meta l4proto udp"
# default: redirect everything except ssh(22)
TCP_RULE="meta l4proto tcp tcp dport != 22 counter redirect to :$local_port"
# default: when PROXY_PORTS present, redirect those udp ports to local_port
UDP_RULE="meta l4proto udp counter tproxy ip to :$local_port meta mark set 0x01"
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_tcp 2>/dev/null | grep -F -- "$TCP_RULE" >/dev/null 2>&1; then
if ! $NFT add rule inet ss_spec ss_spec_wan_fw_tcp $TCP_RULE 2>/dev/null; then
loger 3 "Can't redirect TCP, please check nftables."
return 1
fi
fi
if ! $NFT list chain inet ss_spec ss_spec_wan_fw_udp 2>/dev/null | grep -F -- "$UDP_RULE" >/dev/null 2>&1; then
if ! $NFT add rule inet ss_spec ss_spec_wan_fw_udp $UDP_RULE 2>/dev/null; then
loger 3 "Can't tproxy UDP, 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 $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$local_port
$NFT add rule inet ss_spec ss_spec_wan_ac_udp $UDP_EXT_ARGS ip daddr @netflix counter tproxy ip to :$local_port meta mark set 0x01
;;
*)
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp $TCP_EXT_ARGS ip daddr @netflix counter redirect to :$SHUNT_PORT
$NFT add rule inet ss_spec ss_spec_wan_ac_udp $UDP_EXT_ARGS ip daddr @netflix counter tproxy ip to :$SHUNT_PORT meta mark set 0x01
if [ "$SHUNT_PROXY" = "1" ]; then
$NFT add rule inet ss_spec ss_spec_wan_ac_tcp $TCP_EXT_ARGS ip daddr $SHUNT_IP counter redirect to :$local_port
$NFT add rule inet ss_spec ss_spec_wan_ac_udp $UDP_EXT_ARGS ip daddr $SHUNT_IP counter tproxy ip to :$local_port meta mark set 0x01
else
[ -n "$SHUNT_IP" ] && $NFT add element inet ss_spec whitelist "{ $SHUNT_IP }" 2>/dev/null
fi
;;
esac
fi
return $?
}
fw_rule_iptables() {
$IPT -N SS_SPEC_WAN_FW
$IPT -A SS_SPEC_WAN_FW -d 0.0.0.0/8 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 10.0.0.0/8 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 127.0.0.0/8 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 169.254.0.0/16 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 172.16.0.0/12 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 192.168.0.0/16 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 224.0.0.0/4 -j RETURN
$IPT -A SS_SPEC_WAN_FW -d 240.0.0.0/4 -j RETURN
$IPT -A SS_SPEC_WAN_FW -p tcp $PROXY_PORTS -j REDIRECT --to-ports "$local_port" 2>/dev/null || {
loger 3 "Can't redirect, 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_tcp >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_prerouting_tcp '{ type nat hook prerouting priority 0; policy accept; }'
fi
$NFT flush chain inet ss_spec ss_spec_prerouting_tcp 2>/dev/null
# Exclude special local addresses
if $NFT list chain inet ss_spec ss_spec_prerouting_tcp >/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_tcp ip daddr $net return 2>/dev/null
done
fi
# 暂注释 IPV6 用于后续开启 IPV6
#if $NFT list chain inet ss_spec ss_spec_prerouting_tcp >/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_tcp ip6 daddr $net return 2>/dev/null
# done
#fi
# Create ss_spec_prerouting udp chain
if ! $NFT list chain inet ss_spec ss_spec_prerouting_udp >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_prerouting_udp '{ type filter hook prerouting priority -150; policy accept; }'
fi
$NFT flush chain inet ss_spec ss_spec_prerouting_udp 2>/dev/null
# Exclude special local addresses
if $NFT list chain inet ss_spec ss_spec_prerouting_udp >/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_udp ip daddr $net return 2>/dev/null
done
fi
# 暂注释 IPV6 用于后续开启 IPV6
#if $NFT list chain inet ss_spec ss_spec_prerouting_udp >/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_udp 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 }"
UDP_EXT_ARGS="meta l4proto udp udp dport { $PORTS_ARGS }"
fi
else
TCP_EXT_ARGS="meta l4proto tcp"
UDP_EXT_ARGS="meta l4proto udp"
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 $TCP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null
$NFT add rule inet ss_spec ss_spec_prerouting_udp $UDP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac_udp comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule inet ss_spec ss_spec_prerouting_tcp $TCP_EXT_ARGS jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null
$NFT add rule inet ss_spec ss_spec_prerouting_udp $UDP_EXT_ARGS jump ss_spec_wan_ac_udp comment "\"$TAG\"" 2>/dev/null
fi
else
# For each Interface, find its actual ifname and add an iifname-limited prerouting rule
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
if [ -n "$IFNAME" ]; then
if [ -n "$MATCH_SET" ]; then
$NFT add rule inet ss_spec ss_spec_prerouting_tcp meta iifname "$IFNAME" $TCP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null
$NFT add rule inet ss_spec ss_spec_prerouting_udp meta iifname "$IFNAME" $UDP_EXT_ARGS $MATCH_SET jump ss_spec_wan_ac_udp comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule inet ss_spec ss_spec_prerouting_tcp meta iifname "$IFNAME" $TCP_EXT_ARGS jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null
$NFT add rule inet ss_spec ss_spec_prerouting_udp meta iifname "$IFNAME" $UDP_EXT_ARGS jump ss_spec_wan_ac_udp 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_tcp >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_output_tcp '{ type nat hook output priority 0; policy accept; }'
fi
$NFT flush chain inet ss_spec ss_spec_output_tcp 2>/dev/null
# Exclude special local addresses
if $NFT list chain inet ss_spec ss_spec_output_tcp >/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_tcp ip daddr $net return 2>/dev/null
done
fi
# 暂注释 IPV6 用于后续开启 IPV6
#if $NFT list chain inet ss_spec ss_spec_output_tcp >/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_tcp 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 $TCP_EXT_ARGS jump ss_spec_wan_ac_tcp comment "\"$TAG\"" 2>/dev/null
# Create ss_spec_output udp chain
if ! $NFT list chain inet ss_spec ss_spec_output_udp >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_output_udp '{ type filter hook output priority -150; policy accept; }'
fi
$NFT flush chain inet ss_spec ss_spec_output_udp 2>/dev/null
# Exclude special local addresses
if $NFT list chain inet ss_spec ss_spec_output_udp >/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_udp ip daddr $net return 2>/dev/null
done
fi
# 暂注释 IPV6 用于后续开启 IPV6
#if $NFT list chain inet ss_spec ss_spec_output_udp >/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_udp 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_udp $UDP_EXT_ARGS meta mark set 0x01 comment "\"$TAG\"" 2>/dev/null
;;
2)
# 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
$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_tcp 2>/dev/null
$NFT add rule inet ss_spec ss_spec_output $TCP_EXT_ARGS jump ss_spec_router comment "\"$TAG\"" 2>/dev/null
$NFT add rule inet ss_spec ss_spec_router jump ss_spec_wan_fw_udp 2>/dev/null
$NFT add rule inet ss_spec ss_spec_output $UDP_EXT_ARGS jump ss_spec_router comment "\"$TAG\"" 2>/dev/null
;;
esac
return 0
}
ac_rule_iptables() {
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
if [ -z "$Interface" ]; then
$IPT -I PREROUTING 1 -p tcp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_WAN_AC
else
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
[ -n "$IFNAME" ] && $IPT -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p tcp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_WAN_AC
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 && \
$IPT -A SS_SPEC_ROUTER -m set --match-set ssr_gen_router dst -j RETURN && \
$IPT -A SS_SPEC_ROUTER -j SS_SPEC_WAN_FW
$IPT -I OUTPUT 1 -p tcp -m comment --comment "$TAG" -j SS_SPEC_ROUTER
;;
esac
return $?
}
tp_rule() {
[ -n "$TPROXY" ] || return 0
if [ "$USE_NFT" = "1" ]; then
tp_rule_nft
else
tp_rule_iptables
fi
return $?
}
tp_rule_nft() {
# set up routing table for tproxy
if ! ip rule show | grep -Eq "fwmark 0x0*1.*lookup 100"; then
ip rule add fwmark 0x01/0x01 table 100 2>/dev/null
fi
if ! ip route show table 100 | grep -q "^local.*dev lo"; then
ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null
fi
# create mangle table and tproxy chain
if ! $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then
$NFT add table ip ss_spec_mangle 2>/dev/null
fi
local MATCH_SET=""
if [ -n "$PROXY_PORTS" ]; then
PORTS_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
if [ -n "$PORTS_ARGS" ]; then
EXT_ARGS="udp dport { $PORTS_ARGS }"
else
EXT_ARGS=""
fi
fi
if [ -n "$LAN_AC_IP" ]; then
# Create LAN access control set if needed
if ! $NFT list set ip ss_spec_mangle ss_spec_lan_ac >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle ss_spec_lan_ac '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
else
$NFT flush set ip ss_spec_mangle ss_spec_lan_ac 2>/dev/null
fi
for ip in ${LAN_AC_IP#?}; do
[ -n "$ip" ] && $NFT add element ip ss_spec_mangle ss_spec_lan_ac "{ $ip }" 2>/dev/null
done
case "${LAN_AC_IP%${LAN_AC_IP#?}}" in
w | W)
MATCH_SET="ip saddr @ss_spec_lan_ac"
;;
b | B)
MATCH_SET="ip saddr != @ss_spec_lan_ac"
;;
*)
loger 3 "Bad argument \`-a $LAN_AC_IP\`."
return 2
;;
esac
fi
# Create necessary collections
for setname in china gmlan fplan bplan whitelist; do
if ! $NFT list set ip ss_spec_mangle $setname >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle $setname '{ type ipv4_addr; flags interval; auto-merge; }'
else
$NFT flush set ip ss_spec_mangle $setname 2>/dev/null
fi
done
# Bulk import china ip list safely (avoid huge single element limitation)
if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then
$NFT add element ip ss_spec_mangle china "{ $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') }" 2>/dev/null
fi
# 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
# 暂注释 IPV6 用于后续开启 IPV6
#if $NFT list chain ip ss_spec_mangle ss_spec_tproxy >/dev/null 2>&1; then
# for net in ::1/128 fe80::/10 fc00::/7 ff00::/8 fe80::/10 ::/128 ::ffff:0:0/96; do
# $NFT add rule ip ss_spec_mangle ss_spec_tproxy ip6 daddr $net return 2>/dev/null
# done
#fi
# basic return rules in tproxy chain
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp dport 53 return 2>/dev/null
# avoid redirecting to udp server address
if [ -n "$server" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp dport != 53 ip daddr "$server" return 2>/dev/null
fi
# if server != SERVER add SERVER to whitelist set (so tproxy won't touch it)
if [ -n "$server" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy ip daddr "$server" return 2>/dev/null
fi
if [ -n "$SERVER" ] && [ "$server" != "$SERVER" ]; then
$NFT add element ip ss_spec_mangle whitelist "{ $SERVER }" 2>/dev/null
fi
# access control and tproxy rules
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @bplan return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @fplan counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
# Handle different run modes for nftables
case "$RUNMODE" in
router)
if ! $NFT list set ip ss_spec_mangle ss_spec_wan_ac >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle ss_spec_wan_ac '{ type ipv4_addr; flags interval; auto-merge; }'
else
$NFT flush set ip ss_spec_mangle ss_spec_wan_ac 2>/dev/null
fi
# Add special IP ranges to WAN AC set
for ip in $(gen_spec_iplist); do
[ -n "$ip" ] && $NFT add element ip ss_spec_mangle ss_spec_wan_ac "{ $ip }" 2>/dev/null
done
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @ss_spec_wan_ac return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @china return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp udp dport 80 drop 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr != @ss_spec_wan_ac counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
gfw)
if ! $NFT list set ip ss_spec_mangle gfwlist >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle gfwlist '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
fi
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @china return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp udp dport 80 drop 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @gfwlist counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
oversea)
if ! $NFT list set ip ss_spec_mangle oversea >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle oversea '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
fi
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip saddr @oversea counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS ip daddr @china counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
all)
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp $EXT_ARGS counter tproxy ip to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
esac
# finally, ensure prerouting hook entry to jump to tproxy chain
if ! $NFT list chain ip ss_spec_mangle prerouting >/dev/null 2>&1; then
$NFT add chain ip ss_spec_mangle prerouting '{ type filter hook prerouting priority mangle; policy accept; }'
fi
# add prerouting jump (idempotent)
if [ -z "$Interface" ]; then
# 全局规则
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
# 指定接口
for name in $Interface; do
IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
if [ -n "$IFNAME" ]; then
if [ -n "$MATCH_SET" ]; then
$NFT add rule ip ss_spec_mangle prerouting meta iifname "$IFNAME" meta l4proto udp $EXT_ARGS $MATCH_SET jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule ip ss_spec_mangle prerouting meta iifname "$IFNAME" meta l4proto udp $EXT_ARGS jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
fi
fi
done
fi
return $?
}
tp_rule_iptables() {
ip rule add fwmark 0x01/0x01 table 100
ip route add local 0.0.0.0/0 dev lo table 100
local ipt="iptables -t mangle"
$ipt -N SS_SPEC_TPROXY
$ipt -A SS_SPEC_TPROXY -p udp --dport 53 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 0.0.0.0/8 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 10.0.0.0/8 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 127.0.0.0/8 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 169.254.0.0/16 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 172.16.0.0/12 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 192.168.0.0/16 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 224.0.0.0/4 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp -d 240.0.0.0/4 -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp ! --dport 53 -d "$SERVER" -j RETURN
[ "$server" != "$SERVER" ] && ipset -! add whitelist "$SERVER"
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set bplan src -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set fplan src -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
case "$RUNMODE" in
router)
$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 -m set --match-set gmlan src -m set ! --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set ! --match-set ss_spec_wan_ac dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
;;
gfw)
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set china dst -j RETURN
$ipt -A SS_SPEC_TPROXY -p udp --dport 80 -j DROP
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set gfwlist dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set ! --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
;;
oversea)
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set oversea src -m dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
$ipt -A SS_SPEC_TPROXY -p udp -m set --match-set gmlan src -m set -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -m set --match-set china dst -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
;;
all)
$ipt -A SS_SPEC_TPROXY -p udp $PROXY_PORTS -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
;;
esac
if [ -z "$Interface" ]; then
$ipt -I PREROUTING 1 -p udp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_TPROXY
else
for name in $Interface; do
local IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
[ -n "$IFNAME" ] && $ipt -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p udp $EXT_ARGS $MATCH_SET -m comment --comment "$TAG" -j SS_SPEC_TPROXY
done
fi
return $?
}
get_wan_ip() {
cat <<-EOF | grep -E "^([0-9]{1,3}\.){3}[0-9]{1,3}"
$server
$SERVER
$WAN_BP_IP
EOF
}
gen_spec_iplist() {
cat <<-EOF
0.0.0.0/8
10.0.0.0/8
100.64.0.0/10
127.0.0.0/8
169.254.0.0/16
172.16.0.0/12
192.0.0.0/24
192.0.2.0/24
192.88.99.0/24
192.168.0.0/16
198.18.0.0/15
198.51.100.0/24
203.0.113.0/24
224.0.0.0/4
240.0.0.0/4
255.255.255.255
$(get_wan_ip)
EOF
}
gen_include() {
[ -n "$FWI" ] || return 0
if [ "$USE_NFT" = "1" ]; then
gen_include_nft
else
gen_include_iptables
fi
return $?
}
# 修改gen_include_nft调用持久化功能
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
}
# 检查 nftables 规则状态
check_nftables_status() {
if [ "$USE_NFT" != "1" ]; then
echo "NFTables not in use"
return 0
fi
# 检查ss_spec表是否存在
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
# 检查是否有基本规则
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_rules() {
if [ "$USE_NFT" != "1" ]; then
return 1 # NFTables未使用需要更新
fi
# 如果没有持久化文件,更新持久化文件
if [ ! -f "$NFTABLES_RULES_FILE" ]; then
loger 6 "No persistence file found, update needed"
return 1 # 需要更新持久化文件
fi
# 检查ss_spec表是否存在
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 # 需要更新ss_spec表
fi
# 生成当前规则的临时文件
local temp_file=$(mktemp)
local rules_file=$(mktemp)
loger 7 "DEBUG: Temporary file path: $current_rules_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
# 检查是否成功导出了当前规则
if [ ! -s "$rules_file" ] || ! grep -q "table" "$rules_file" 2>/dev/null; then
loger 4 "Failed to export current rules"
rm -f "$temp_file" "$rules_file"
return 1 # 导出失败,需要更新
fi
# 比较当前规则与持久化文件中的规则
if ! cmp -s "$rules_file" "$NFTABLES_RULES_FILE"; then
loger 6 "Rules differ, update needed"
rm -f "$temp_file" "$rules_file"
return 1 # 需要更新
fi
rm -f "$temp_file" "$rules_file"
loger 6 "Rules unchanged, no update needed"
return 0 # 无需更新
}
# 自动更新持久化规则
persist_nftables_rules() {
if [ "$USE_NFT" != "1" ]; then
return 0
fi
# 如果模式未改变且存在持久化文件,跳过更新
if [ "$MODE_CHANGED" = "0" ] && [ -f "$NFTABLES_RULES_FILE" ]; then
loger 6 "Mode unchanged and persistence file exists, skipping update"
return 0
fi
# 强制更新时,跳过比较检查并删除旧文件
if [ "$FORCE_UPDATE" = "1" ]; then
loger 6 "Force update requested, removing old persistence file"
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
# 非强制更新时,进行规则比较
elif [ -f "$NFTABLES_RULES_FILE" ]; then
if compare_rules; then
loger 6 "Rules unchanged, skipping persistence update"
return 0
fi
fi
# 确保目录存在
mkdir -p "$NFTABLES_RULES_DIR" 2>/dev/null
# 生成nftables规则文件
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 "" >> "$NFTABLES_RULES_FILE"
local HAS_RULES=0
# 分别导出每个表
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
# 检查是否成功导出了规则
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
# 设置文件权限
chmod 644 "$NFTABLES_RULES_FILE" 2>/dev/null
# 记录成功信息
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
}
# 自动更新守护进程
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_auto_update_daemon
# 直接在后台启动守护进程
(
logger -t ssr-rules[daemon] "Auto-update daemon started - PID: $$"
echo $$ > "/var/run/ssr-rules-daemon.pid"
while true; do
sleep 300
if [ -x "/usr/bin/ssr-rules" ]; then
if /usr/bin/ssr-rules -C >/dev/null 2>&1; then
logger -t ssr-rules[daemon] "Rules changed or missing, updating persistence"
if /usr/bin/ssr-rules -P >/dev/null 2>&1; then
logger -t ssr-rules[daemon] "Persistence rules updated successfully"
else
logger -t ssr-rules[daemon] "Failed to update persistence"
fi
else
logger -t ssr-rules[daemon] "Rules status OK, no update needed"
fi
else
logger -t ssr-rules[daemon] "Script not found, exiting daemon"
exit 1
fi
done
) &
local DAEMON_PID=$!
sleep 2
if kill -0 "$DAEMON_PID" 2>/dev/null; then
loger 6 "Auto-update daemon started with PID: $DAEMON_PID"
return 0
else
loger 3 "Auto-update daemon failed to start"
return 1
fi
}
# 停止自动更新守护进程函数
stop_auto_update_daemon() {
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() {
if [ "$USE_NFT" != "1" ]; then
echo "NFTables not in use"
return 0
fi
# 移除现有规则文件确保重新创建
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
# 调用持久化函数
if persist_nftables_rules; then
loger 5 "Persistence update completed successfully"
return 0
else
loger 3 "Persistence update failed"
return 1
fi
}
# 从持久化文件恢复规则
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"
# 清理现有规则
flush_r
# 从文件恢复规则
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
;;
# 新增持久化管理选项
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
# 首先处理需要立即退出的选项
if [ "$CHECK_STATUS" = "1" ]; then
check_nftables_status
exit $?
fi
if [ "$STOP_AUTO_UPDATE" = "1" ]; then
stop_auto_update_daemon
exit 0
fi
# 只有-X选项执行清理后退出
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
# 检查是否有持久化管理选项单独处理
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
# 处理持久化管理选项的情况
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
# 强制刷新规则
if [ "$FLUSH_RULES" = "1" ]; then
flush_r
# 如果只有 -f 选项,则退出
if [ -z "$server" ] && [ -z "$local_port" ] && [ "$FORCE_UPDATE" != "1" ] && \
[ "$RESTORE_RULES" != "1" ] && [ "$ENABLE_AUTO_UPDATE" != "1" ] && \
[ "$CLEANUP_PERSISTENCE" != "1" ]; then
exit 0
fi
fi
# 从持久化文件恢复规则(在规则应用之前)
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
# 运行模式更改
runmode_change() {
local mode_file="/tmp/.ssr_run_mode"
local new_mode=""
local old_mode=""
# 从参数获取模式
if [ -n "$1" ]; then
new_mode="$1"
fi
# 从文件中读取上一次的运行模式
if [ -f "$mode_file" ]; then
old_mode=$(cat "$mode_file" 2>/dev/null)
fi
# 比较模式是否改变
if [ "$old_mode" = "$new_mode" ] && [ -n "$old_mode" ]; then
# 模式未改变
echo "$new_mode" > "$mode_file" # 更新文件时间戳
loger 6 "Runmode unchanged: $new_mode"
return 1 # 返回1表示未改变
else
# 模式已改变或首次运行
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 # 返回0表示已改变
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
# 保存上一次 TPROXY 状态文件
TPROXY_STATE_FILE="/tmp/.last_tproxy"
if [ -f "$TPROXY_STATE_FILE" ]; then
LAST_TPROXY=$(cat "$TPROXY_STATE_FILE")
else
LAST_TPROXY=""
fi
# 保存上一次 PROXY_PORTS 状态
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
# STEP 1: 判断 TPROXY 是否有值1 或 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: 判断 PROXY_PORTS 是否有值(非空字符串)
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 3: 判断是否需要强制重建
FORCE_RECREATE=0
PERSISTENCE_EXISTS=0
# 触发条件:
# 1. TPROXY 从空 ↔ 有值变化
# 2. PROXY_PORTS 从空 ↔ 有值变化
if [ "$TPROXY_HAS_VALUE" != "$LAST_HAS_VALUE" ] || [ "$PROXY_HAS_VALUE" != "$LAST_PROXY_HAS_VALUE" ]; then
FORCE_RECREATE=1
loger 6 "TPROXY or PROXY_PORTS changed → force rebuild rules"
rm -f "$NFTABLES_RULES_FILE" 2>/dev/null
else
# 未触发 FORCE_RECREATE → 检查持久化文件
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: 保存当前状态
echo "$TPROXY" > "$TPROXY_STATE_FILE"
echo "$PROXY_PORTS" > "$PROXY_PORTS_STATE_FILE"
# STEP 5: 判断运行模式是否改变
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: 模式改变且持久化存在 → 删除一次
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 优先 → 必须重建规则
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: 持久化存在 → 尝试 restore
if [ "$PERSISTENCE_EXISTS" = "1" ]; then
# 恢复规则
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: 持久化不存在或 restore 失败 → 生成新规则
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