1729 lines
57 KiB
Bash
Executable File
1729 lines
57 KiB
Bash
Executable File
#!/bin/sh
|
||
#
|
||
# Copyright (C) 2017 openwrt-ssr
|
||
# Copyright (C) 2017 yushi studio <ywb94@qq.com>
|
||
#
|
||
# This is free software, licensed under the GNU General Public License v3.
|
||
# See /LICENSE for more information.
|
||
#
|
||
|
||
# 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
|