Files
openwrt_packages/luci-app-ssr-plus/root/usr/bin/ssr-rules
2025-11-18 00:14:08 +08:00

1030 lines
34 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
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
-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
}
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"
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; 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
# 批量导入中国IP列表
if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then
$NFT add element inet ss_spec china "{ $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') }" 2>/dev/null
fi
# Add IP addresses to sets
for ip in $LAN_GM_IP; do
[ -n "$ip" ] && $NFT add element inet ss_spec gmlan "{ $ip }" 2>/dev/null
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 chain for WAN access control
if ! $NFT list chain inet ss_spec ss_spec_wan_ac >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_wan_ac 2>/dev/null
fi
$NFT flush chain inet ss_spec ss_spec_wan_ac 2>/dev/null
# Create forward chain with better error handling
if ! $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_wan_fw 2>/dev/null || {
loger 3 "Failed to create forward chain"
return 1
}
fi
# Clear existing rules
$NFT flush chain inet ss_spec ss_spec_wan_fw 2>/dev/null
# Add basic rules
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport 53 ip daddr 127.0.0.0/8 return
$NFT add rule inet ss_spec ss_spec_wan_ac tcp dport != 53 ip daddr "$server" return
# Set up mode-specific rules
case "$RUNMODE" in
router)
if ! $NFT list set inet ss_spec ss_spec_wan_ac >/dev/null 2>&1; then
$NFT add set inet ss_spec ss_spec_wan_ac '{ type ipv4_addr; flags interval; auto-merge; }'
else
$NFT flush set inet ss_spec ss_spec_wan_ac 2>/dev/null
fi
# Add special IP ranges to WAN AC set
for ip in $(gen_spec_iplist); do
[ -n "$ip" ] && $NFT add element inet ss_spec ss_spec_wan_ac "{ $ip }" 2>/dev/null
done
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @ss_spec_wan_ac return
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china return 2>/dev/null
if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_ac jump ss_spec_wan_fw
fi
;;
gfw)
if ! $NFT list set inet ss_spec gfwlist >/dev/null 2>&1; then
$NFT add set inet ss_spec gfwlist '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
fi
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china return 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @gfwlist jump ss_spec_wan_fw 2>/dev/null
if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan ip daddr != @china jump ss_spec_wan_fw
fi
;;
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
if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
$NFT insert rule inet ss_spec ss_spec_wan_ac ip daddr @oversea jump ss_spec_wan_fw 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @gmlan jump ss_spec_wan_fw 2>/dev/null
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @china jump ss_spec_wan_fw 2>/dev/null
fi
;;
all)
if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
$NFT add rule inet ss_spec ss_spec_wan_ac jump ss_spec_wan_fw
fi
;;
esac
# Access control rules
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @fplan jump ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_ac ip saddr @bplan return
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @blacklist jump ss_spec_wan_fw
$NFT add rule inet ss_spec ss_spec_wan_ac ip daddr @whitelist return
# Music unlocking support
if $NFT list set inet ss_spec music >/dev/null 2>&1; then
$NFT insert rule inet ss_spec ss_spec_wan_ac ip daddr @music return 2>/dev/null
fi
# Shunt/Netflix rules
if [ "$SHUNT_PORT" != "0" ] && [ -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
case "$SHUNT_PORT" in
1)
$NFT insert rule inet ss_spec ss_spec_wan_ac ip daddr @netflix meta l4proto tcp redirect to :"$local_port"
;;
*)
$NFT insert rule inet ss_spec ss_spec_wan_ac ip daddr @netflix meta l4proto tcp redirect to :"$SHUNT_PORT"
if [ "$SHUNT_PROXY" = "1" ]; then
$NFT insert rule inet ss_spec ss_spec_wan_ac ip daddr "$SHUNT_IP" meta l4proto tcp redirect to :"$local_port"
else
[ -n "$SHUNT_IP" ] && $NFT add element inet ss_spec whitelist "{ $SHUNT_IP }" 2>/dev/null
fi
;;
esac
fi
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() {
# Exclude special local addresses
if $NFT list chain inet ss_spec ss_spec_wan_fw >/dev/null 2>&1; then
for net in 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do
$NFT add rule inet ss_spec ss_spec_wan_fw ip daddr $net return 2>/dev/null
done
fi
# redirect/translation: when PROXY_PORTS present, redirect those tcp ports to local_port
if [ -n "$PROXY_PORTS" ]; then
PORTS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
RULE="tcp dport { $PORTS } redirect to :"$local_port""
else
# default: redirect everything except ssh(22)
RULE="tcp dport != 22 redirect to :"$local_port""
fi
if ! $NFT list chain inet ss_spec ss_spec_wan_fw 2>/dev/null | grep -q "$RULE"; then
if ! $NFT add rule inet ss_spec ss_spec_wan_fw $RULE 2>/dev/null; then
loger 3 "Can't redirect, please check nftables."
return 1
fi
fi
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
# 创建ss_spec_prerouting链
if ! $NFT list chain inet ss_spec ss_spec_prerouting >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_prerouting '{ type nat hook prerouting priority -150; policy accept; }'
fi
$NFT flush chain inet ss_spec ss_spec_prerouting 2>/dev/null
# 创建ss_spec_output链
if ! $NFT list chain inet ss_spec ss_spec_output >/dev/null 2>&1; then
$NFT add chain inet ss_spec ss_spec_output '{ type nat hook output priority -100; policy accept; }'
fi
$NFT flush chain inet ss_spec ss_spec_output 2>/dev/null
# Build a rule in the prerouting hook chain that jumps to business chain with conditions
if [ -n "$PROXY_PORTS" ]; then
EXT_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
fi
if [ -z "$Interface" ]; then
# generic prerouting jump already exists (see ipset_nft), but if we have MATCH_SET_CONDITION we add a more specific rule
if [ -n "$MATCH_SET" ]; then
# add a more specific rule at the top of ss_spec_prerouting
$NFT insert rule inet ss_spec ss_spec_prerouting meta l4proto tcp th dport { $EXT_ARGS } $MATCH_SET jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
else
$NFT insert rule inet ss_spec ss_spec_prerouting meta l4proto tcp th dport { $EXT_ARGS } jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
fi
else
# For each Interface, find its actual ifname and add an iifname-limited prerouting rule
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 insert rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" meta l4proto tcp th dport { $EXT_ARGS } $MATCH_SET jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
else
$NFT insert rule inet ss_spec ss_spec_prerouting meta iifname "$IFNAME" meta l4proto tcp th dport { $EXT_ARGS } jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
fi
fi
done
fi
case "$OUTPUT" in
1)
# create output hook chain & route output traffic into router chain
$NFT insert rule inet ss_spec ss_spec_output meta l4proto tcp th dport { $EXT_ARGS } jump ss_spec_wan_ac comment "\"$TAG\"" 2>/dev/null
;;
2)
# router mode output chain: create ssr_gen_router set & router chain
$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 2>/dev/null
$NFT add rule inet ss_spec ss_spec_output meta l4proto tcp th dport { $EXT_ARGS } jump ss_spec_router comment "\"$TAG\"" 2>/dev/null
;;
esac
return 0
}
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
ip rule add fwmark 0x01/0x01 table 100 2>/dev/null
ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null
# create mangle table and tproxy chain
if ! $NFT list table ip ss_spec_mangle >/dev/null 2>&1; then
$NFT add table ip ss_spec_mangle 2>/dev/null
fi
local MATCH_SET=""
local EXT_ARGS=""
if [ -n "$PROXY_PORTS" ]; then
EXT_ARGS=$(echo "$PROXY_PORTS" | sed 's/-m multiport --dports //')
fi
if [ -n "$LAN_AC_IP" ]; then
# Create LAN access control set if needed
if ! $NFT list set ip ss_spec_mangle ss_spec_lan_ac >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle ss_spec_lan_ac '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
else
$NFT flush set ip ss_spec_mangle ss_spec_lan_ac 2>/dev/null
fi
for ip in ${LAN_AC_IP#?}; do
[ -n "$ip" ] && $NFT add element ip ss_spec_mangle ss_spec_lan_ac "{ $ip }" 2>/dev/null
done
case "${LAN_AC_IP%${LAN_AC_IP#?}}" in
w | W)
MATCH_SET="ip saddr @ss_spec_lan_ac"
;;
b | B)
MATCH_SET="ip saddr != @ss_spec_lan_ac"
;;
*)
loger 3 "Bad argument \`-a $LAN_AC_IP\`."
return 2
;;
esac
fi
# Create necessary collections
for setname in china gmlan fplan bplan whitelist; do
if ! $NFT list set ip ss_spec_mangle $setname >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle $setname '{ type ipv4_addr; flags interval; auto-merge; }'
else
$NFT flush set ip ss_spec_mangle $setname 2>/dev/null
fi
done
# 批量导入中国IP列表
if [ -f "${china_ip:=/etc/ssrplus/china_ssr.txt}" ]; then
$NFT add element ip ss_spec_mangle china "{ $(tr '\n' ',' < "${china_ip}" | sed 's/,$//') }" 2>/dev/null
fi
# use priority mangle for compatibility with other rules
if ! $NFT list chain ip ss_spec_mangle ss_spec_tproxy >/dev/null 2>&1; then
$NFT add chain ip ss_spec_mangle ss_spec_tproxy 2>/dev/null
else
$NFT flush chain ip ss_spec_mangle ss_spec_tproxy 2>/dev/null
fi
# basic return rules in tproxy chain
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 53 return 2>/dev/null
if $NFT list chain ip ss_spec_mangle ss_spec_tproxy >/dev/null 2>&1; then
for net in 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4; do
$NFT add rule ip ss_spec_mangle ss_spec_tproxy ip daddr $net return 2>/dev/null
done
fi
# avoid redirecting to udp server address
if [ -n "$server" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport != 53 ip daddr "$server" return 2>/dev/null
fi
# if server != SERVER add SERVER to whitelist set (so tproxy won't touch it)
if [ -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
if [ -n "$EXT_ARGS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip saddr @fplan tproxy to :"$LOCAL_PORT" meta mark set 0x01
else
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @fplan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
# Handle different run modes for nftables
case "$RUNMODE" in
router)
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 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 tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
if [ -n "$EXT_ARGS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip daddr != @ss_spec_wan_ac tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
else
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr != @ss_spec_wan_ac tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
;;
gfw)
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip daddr @china return 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport 80 drop 2>/dev/null
if [ -n "$EXT_ARGS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip daddr @gfwlist tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan ip daddr != @china tproxy 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
if ! $NFT list set ip ss_spec_mangle china >/dev/null 2>&1; then
$NFT add set ip ss_spec_mangle china '{ type ipv4_addr; flags interval; auto-merge; }' 2>/dev/null
fi
if [ -n "$EXT_ARGS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip saddr @oversea tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } ip daddr @china tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp ip saddr @gmlan tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
;;
all)
if [ -n "$EXT_ARGS" ]; then
$NFT add rule ip ss_spec_mangle ss_spec_tproxy udp dport { $EXT_ARGS } tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
else
$NFT add rule ip ss_spec_mangle ss_spec_tproxy meta l4proto udp tproxy to :"$LOCAL_PORT" meta mark set 0x01 2>/dev/null
fi
;;
esac
# 创建 prerouting 链hook prerouting
if ! $NFT list chain ip ss_spec_mangle prerouting >/dev/null 2>&1; then
$NFT add chain ip ss_spec_mangle prerouting '{ type filter hook prerouting priority mangle; policy accept; }'
fi
# 添加规则到 prerouting 链
if [ -z "$Interface" ]; then
# 全局规则
if [ -n "$MATCH_SET" ]; then
$NFT add rule ip ss_spec_mangle prerouting udp dport { $EXT_ARGS } $MATCH_SET jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule ip ss_spec_mangle prerouting udp dport { $EXT_ARGS } jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
fi
else
# 指定接口
for name in $Interface; do
IFNAME=$(uci -P /var/state get network."$name".ifname 2>/dev/null)
[ -z "$IFNAME" ] && IFNAME=$(uci -P /var/state get network."$name".device 2>/dev/null)
if [ -n "$IFNAME" ]; then
if [ -n "$MATCH_SET" ]; then
$NFT add rule ip ss_spec_mangle prerouting meta iifname "$IFNAME" udp dport { $EXT_ARGS } $MATCH_SET jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
else
$NFT add rule ip ss_spec_mangle prerouting meta iifname "$IFNAME" udp dport { $EXT_ARGS } jump ss_spec_tproxy comment "\"$TAG\"" 2>/dev/null
fi
fi
done
fi
return $?
}
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() {
# 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
nft list ruleset | awk '/^table (inet|ip) ss_spec/{flag=1} /^table / && !/^table (inet|ip) ss_spec/{flag=0} flag'
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
}
while getopts ":m:s:l:S:L:i:e:a:B:b:w:p:G:D:F:N:M:I:oOuUfgrczh" 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
;;
f)
flush_r
exit 0
;;
h) usage 0 ;;
esac
done
if [ -z "$server" ] || [ -z "$local_port" ]; then
usage 2
fi
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
# First check whether nftables is working properly
if [ "$USE_NFT" = "1" ]; then
if ! $NFT list tables 2>/dev/null; then
loger 3 "nftables is not working properly, check if nftables is installed and running"
exit 1
fi
fi
if [ "$USE_NFT" = "1" ]; then
# NFTables
if flush_r && ipset_r && fw_rule && ac_rule && tp_rule && gen_include; then
loger 5 "NFTables rules applied successfully"
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