mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-07-27 08:31:44 +08:00
2252 lines
71 KiB
Bash
Executable File
2252 lines
71 KiB
Bash
Executable File
#!/bin/sh /etc/rc.common
|
||
#
|
||
# Copyright (C) 2017 openwrt-ssr
|
||
# Copyright (C) 2017 yushi studio <ywb94@qq.com>
|
||
# Copyright (C) 2018 lean <coolsnowwolf@gmail.com>
|
||
# Copyright (C) 2020 Mattraks <mattraks@gmail.com>
|
||
#
|
||
# This is free software, licensed under the GNU General Public License v3.
|
||
# See /LICENSE for more information.
|
||
#
|
||
|
||
START=95
|
||
STOP=15
|
||
SERVICE_DAEMONIZE=1
|
||
NAME=shadowsocksr
|
||
LOCK_FILE=/var/lock/ssrplus.lock
|
||
LOG_FILE=/var/log/ssrplus.log
|
||
TMP_PATH=/var/etc/ssrplus
|
||
TMP_BIN_PATH=$TMP_PATH/bin
|
||
PERSIST_DIR="/usr/share/nftables.d/ruleset-post"
|
||
PERSIST_FILE="$PERSIST_DIR/99-shadowsocksr.nft"
|
||
BACKUP_DIR="/etc/ssrplus/ssrplus-persist"
|
||
BACKUP_FILE="$BACKUP_DIR/99-shadowsocksr.save"
|
||
CLASH_CONFIG_DIR="/etc/ssrplus/clash"
|
||
CLASH_API_PORT="16756"
|
||
CLASH_OUTBOUND_MARK="255"
|
||
CLASH_YAML_HELPER="/usr/share/shadowsocksr/clash_yaml.lua"
|
||
# 设置 DNSMASQ_CONF_DIR 和 TMP_DNSMASQ_PATH
|
||
if [ -f /etc/openwrt_release ]; then
|
||
# 获取默认的 DNSMASQ 配置 ID
|
||
DEFAULT_DNSMASQ_CFGID="$(uci -q show "dhcp.@dnsmasq[0]" | awk 'NR==1 {split($0, conf, /[.=]/); print conf[2]}')"
|
||
# 从 conf-dir 行中提取配置目录路径
|
||
if [ -f "/tmp/etc/dnsmasq.conf.$DEFAULT_DNSMASQ_CFGID" ]; then
|
||
DNSMASQ_CONF_DIR="$(awk -F '=' '/^conf-dir=/ {print $2}' "/tmp/etc/dnsmasq.conf.$DEFAULT_DNSMASQ_CFGID")"
|
||
else
|
||
DNSMASQ_CONF_DIR="/tmp/dnsmasq.d"
|
||
fi
|
||
# 设置 TMP_DNSMASQ_PATH,并去除路径末尾的斜杠
|
||
TMP_DNSMASQ_PATH="${DNSMASQ_CONF_DIR%*/}/dnsmasq-ssrplus.d"
|
||
fi
|
||
|
||
chain_config_file= #generate shadowtls chain proxy config file
|
||
tcp_config_file=
|
||
udp_config_file=
|
||
shunt_config_file=
|
||
local_config_file=
|
||
shunt_dns_config_file=
|
||
tmp_local_port=
|
||
DNS2TCP_FILTER_AAAA_SUPPORTED=
|
||
|
||
ARG_UDP=
|
||
ARG_UDP_RULES=
|
||
|
||
dns_port="5335" #dns port
|
||
china_dns_port="5333" #china_dns_port
|
||
tmp_dns_port="300" #dns2socks temporary port
|
||
tmp_udp_port="301" #udp temporary port
|
||
tmp_udp_local_port="302" #udp socks temporary port
|
||
tmp_shunt_port="303" #shunt temporary port
|
||
tmp_shunt_local_port="304" #shunt socks temporary port
|
||
tmp_shunt_dns_port="305" #shunt dns2socks temporary port
|
||
tmp_tcp_local_port="306" #tcp socks temporary port
|
||
|
||
server_count=0
|
||
redir_tcp=0
|
||
redir_udp=0
|
||
local_enable=0
|
||
http_enable=0
|
||
kcp_enable_flag=0
|
||
pdnsd_enable_flag=0
|
||
|
||
USE_TABLES=""
|
||
HAS_NFT=0
|
||
HAS_IPSET=0
|
||
HAS_IPT=0
|
||
HAS_FW3=0
|
||
HAS_FW4=0
|
||
DNSMASQ_IPSET=0
|
||
DNSMASQ_NFTSET=0
|
||
|
||
switch_server=$1
|
||
CRON_FILE=/etc/crontabs/root
|
||
EXTRA_COMMANDS='reset clash_cache'
|
||
EXTRA_HELP=" reset Reset to default settings
|
||
clash_cache Download and cache Clash node config"
|
||
#extra_command "reset" "Reset to default settings"
|
||
PS="/bin/busybox ps"
|
||
|
||
ps_list() {
|
||
if $PS -w >/dev/null 2>&1; then
|
||
$PS -w
|
||
else
|
||
$PS
|
||
fi
|
||
}
|
||
|
||
uci_get_by_name() {
|
||
local ret=$(uci get $NAME.$1.$2 2>/dev/null)
|
||
echo ${ret:=$3}
|
||
}
|
||
|
||
uci_get_by_type() {
|
||
local ret=$(uci get $NAME.@$1[0].$2 2>/dev/null)
|
||
echo "${ret:=$3}"
|
||
}
|
||
|
||
uci_set_by_name() {
|
||
uci set $NAME.$1.$2=$3 2>/dev/null
|
||
uci commit $NAME
|
||
}
|
||
|
||
uci_set_by_type() {
|
||
uci set $NAME.@$1[0].$2=$3 2>/dev/null
|
||
uci commit $NAME
|
||
}
|
||
|
||
uci_get_by_cfgid() {
|
||
local key="$NAME.@$1[0]"
|
||
[ -n "$2" ] && key="$key.$2"
|
||
local ret=$(uci show "$key" 2>/dev/null | awk -F '[.=]' 'NR==1 {print $2}')
|
||
echo ${ret:=$3}
|
||
}
|
||
|
||
get_filter_aaaa() {
|
||
local ret="$(uci get $NAME.@global[0].filter_aaaa 2>/dev/null)"
|
||
[ -z "$ret" ] && ret="$(uci get $NAME.@global[0].mosdns_ipv6 2>/dev/null)"
|
||
echo "${ret:=1}"
|
||
}
|
||
|
||
supports_dns2tcp_filter_aaaa() {
|
||
local dns2tcp_bin="${1:-$(first_type dns2tcp)}"
|
||
|
||
[ -n "$dns2tcp_bin" ] || return 1
|
||
|
||
if [ -n "$DNS2TCP_FILTER_AAAA_SUPPORTED" ]; then
|
||
[ "$DNS2TCP_FILTER_AAAA_SUPPORTED" = "1" ]
|
||
return $?
|
||
fi
|
||
|
||
if "$dns2tcp_bin" -h 2>&1 | grep -Eq '(^|[[:space:]])-A([[:space:]]|$)'; then
|
||
DNS2TCP_FILTER_AAAA_SUPPORTED=1
|
||
return 0
|
||
fi
|
||
|
||
DNS2TCP_FILTER_AAAA_SUPPORTED=0
|
||
return 1
|
||
}
|
||
|
||
start_dns2tcp() {
|
||
local dnsserver="$1"
|
||
local dns2tcp_bin="$(first_type dns2tcp)"
|
||
|
||
if [ "$(get_filter_aaaa)" = "1" ]; then
|
||
if supports_dns2tcp_filter_aaaa "$dns2tcp_bin"; then
|
||
ln_start_bin "$dns2tcp_bin" dns2tcp -L "127.0.0.1#$dns_port" -R "${dnsserver/:/#}" -A
|
||
else
|
||
echolog "警告:当前 dns2tcp 不支持 AAAA 过滤参数(-A),已改为不带该参数启动。请升级 dns2tcp 版本。"
|
||
ln_start_bin "$dns2tcp_bin" dns2tcp -L "127.0.0.1#$dns_port" -R "${dnsserver/:/#}"
|
||
fi
|
||
else
|
||
ln_start_bin "$dns2tcp_bin" dns2tcp -L "127.0.0.1#$dns_port" -R "${dnsserver/:/#}"
|
||
fi
|
||
}
|
||
|
||
supports_builtin_dns() {
|
||
case "$1" in
|
||
clash|tuic|v2ray|ss)
|
||
return 0
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
get_builtin_dns_fallback_mode() {
|
||
if is_finded "dns2tcp"; then
|
||
echo "1"
|
||
elif is_finded "chinadns-ng"; then
|
||
echo "6"
|
||
elif is_finded "mosdns"; then
|
||
echo "4"
|
||
else
|
||
echo "0"
|
||
fi
|
||
}
|
||
|
||
get_dns2tcp_fallback_mode() {
|
||
if is_finded "dns2tcp"; then
|
||
echo "1"
|
||
else
|
||
echo "0"
|
||
fi
|
||
}
|
||
|
||
force_dns2tcp_fallback() {
|
||
local reason="$1"
|
||
local fallback_mode="$(get_dns2tcp_fallback_mode)"
|
||
|
||
if [ "$fallback_mode" = "1" ]; then
|
||
echolog "提示:${reason},强制回退到 dns2tcp 模式。"
|
||
echo "1"
|
||
return 0
|
||
fi
|
||
|
||
echolog "错误:${reason},且未安装 dns2tcp,启动终止。"
|
||
return 1
|
||
}
|
||
|
||
is_builtin_dns_active() {
|
||
local global_server_type="$(uci_get_by_name "$GLOBAL_SERVER" type)"
|
||
case "$global_server_type" in
|
||
clash|tuic|ss)
|
||
ps_list | grep -v "grep" | grep -q "ssr-retcp"
|
||
;;
|
||
v2ray)
|
||
ps_list | grep -v "grep" | grep -q "$TMP_PATH/.*ssr-retcp\\.json"
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
get_default_node_local_port() {
|
||
uci_get_by_type global default_node_local_port 1234
|
||
}
|
||
|
||
get_configured_threads() {
|
||
local threads
|
||
if [ "$(uci_get_by_type global threads 0)" = "0" ]; then
|
||
threads=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null)
|
||
else
|
||
threads=$(uci_get_by_type global threads 1)
|
||
fi
|
||
|
||
case "$threads" in
|
||
''|*[!0-9]*)
|
||
threads=1
|
||
;;
|
||
esac
|
||
|
||
[ "$threads" -lt 1 ] && threads=1
|
||
echo "$threads"
|
||
}
|
||
|
||
get_clash_api_secret() {
|
||
echo "${1}_ssrplus_clash"
|
||
}
|
||
|
||
get_clash_workdir() {
|
||
echo "$TMP_PATH/clash-$1"
|
||
}
|
||
|
||
get_clash_cache_file() {
|
||
echo "$CLASH_CONFIG_DIR/$1.yaml"
|
||
}
|
||
|
||
get_clash_state_file() {
|
||
echo "$CLASH_CONFIG_DIR/$1.cache.db"
|
||
}
|
||
|
||
get_tuic_workdir() {
|
||
echo "$TMP_PATH/tuic-$1"
|
||
}
|
||
|
||
get_tuic_runtime_file() {
|
||
echo "$(get_tuic_workdir "$1")/config.yaml"
|
||
}
|
||
|
||
get_ss_mihomo_workdir() {
|
||
echo "$TMP_PATH/ss-$1"
|
||
}
|
||
|
||
get_ss_mihomo_runtime_file() {
|
||
echo "$(get_ss_mihomo_workdir "$1")/config.yaml"
|
||
}
|
||
|
||
get_ss_server_mihomo_workdir() {
|
||
echo "$TMP_PATH/ss-server-$1"
|
||
}
|
||
|
||
get_ss_server_mihomo_runtime_file() {
|
||
echo "$(get_ss_server_mihomo_workdir "$1")/config.yaml"
|
||
}
|
||
|
||
link_mihomo_geodata() {
|
||
local workdir="$1"
|
||
|
||
[ -n "$workdir" ] || return 0
|
||
|
||
[ -s /usr/share/shadowsocksr/Country.mmdb ] && ln -sf /usr/share/shadowsocksr/Country.mmdb "$workdir/Country.mmdb"
|
||
[ -s /usr/share/shadowsocksr/Country.mmdb ] && ln -sf /usr/share/shadowsocksr/Country.mmdb "$workdir/geoip.metadb"
|
||
[ ! -e "$workdir/Country.mmdb" ] && [ -s /etc/openclash/Country.mmdb ] && ln -sf /etc/openclash/Country.mmdb "$workdir/Country.mmdb"
|
||
[ ! -e "$workdir/geoip.metadb" ] && [ -s /etc/openclash/Country.mmdb ] && ln -sf /etc/openclash/Country.mmdb "$workdir/geoip.metadb"
|
||
|
||
if [ -s /usr/share/v2ray/geosite.dat ]; then
|
||
ln -sf /usr/share/v2ray/geosite.dat "$workdir/GeoSite.dat"
|
||
ln -sf /usr/share/v2ray/geosite.dat "$workdir/geosite.dat"
|
||
else
|
||
[ -s /etc/openclash/GeoSite.dat ] && ln -sf /etc/openclash/GeoSite.dat "$workdir/GeoSite.dat"
|
||
[ -s /etc/openclash/geosite.dat ] && ln -sf /etc/openclash/geosite.dat "$workdir/geosite.dat"
|
||
fi
|
||
|
||
if [ -s /usr/share/v2ray/geoip.dat ]; then
|
||
ln -sf /usr/share/v2ray/geoip.dat "$workdir/geoip.dat"
|
||
else
|
||
[ -s /etc/openclash/geoip.dat ] && ln -sf /etc/openclash/geoip.dat "$workdir/geoip.dat"
|
||
fi
|
||
}
|
||
|
||
prepare_tuic_runtime_config() {
|
||
local sid="$1"
|
||
local local_port="$2"
|
||
local socks_port="$3"
|
||
local instance_key="${4:-$sid}"
|
||
local run_mode="${5:-redir}"
|
||
local runtime_file="$(get_tuic_runtime_file "$instance_key")"
|
||
local workdir="$(get_tuic_workdir "$instance_key")"
|
||
|
||
[ -n "$sid" ] || return 1
|
||
[ -n "$local_port" ] || return 1
|
||
|
||
mkdir -p "$workdir"
|
||
if ! /usr/bin/lua "$CLASH_YAML_HELPER" tuic "$sid" "$runtime_file" "$local_port" "$socks_port" "$run_mode" >/dev/null 2>&1; then
|
||
echolog "TUIC 节点运行配置生成失败:$sid"
|
||
return 1
|
||
fi
|
||
|
||
# Reuse existing local geodata files so Mihomo does not have to fetch them.
|
||
link_mihomo_geodata "$workdir"
|
||
}
|
||
|
||
prepare_ss_mihomo_runtime_config() {
|
||
local sid="$1"
|
||
local local_port="$2"
|
||
local socks_port="$3"
|
||
local instance_key="${4:-$sid}"
|
||
local run_mode="${5:-redir}"
|
||
local runtime_file="$(get_ss_mihomo_runtime_file "$instance_key")"
|
||
local workdir="$(get_ss_mihomo_workdir "$instance_key")"
|
||
|
||
[ -n "$sid" ] || return 1
|
||
[ -n "$local_port" ] || return 1
|
||
|
||
mkdir -p "$workdir"
|
||
if ! /usr/bin/lua "$CLASH_YAML_HELPER" ss "$sid" "$runtime_file" "$local_port" "$socks_port" "$run_mode" >/dev/null 2>&1; then
|
||
echolog "Shadowsocks 单节点 Mihomo 运行配置生成失败:$sid"
|
||
return 1
|
||
fi
|
||
|
||
link_mihomo_geodata "$workdir"
|
||
}
|
||
|
||
prepare_ss_server_mihomo_runtime_config() {
|
||
local sid="$1"
|
||
local runtime_file="$(get_ss_server_mihomo_runtime_file "$sid")"
|
||
local workdir="$(get_ss_server_mihomo_workdir "$sid")"
|
||
|
||
[ -n "$sid" ] || return 1
|
||
|
||
mkdir -p "$workdir"
|
||
if ! /usr/bin/lua "$CLASH_YAML_HELPER" ss_server "$sid" "$runtime_file" >/dev/null 2>&1; then
|
||
echolog "Shadowsocks 服务端 Mihomo 配置生成失败:$sid"
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
download_clash_config() {
|
||
local sid="$1"
|
||
local clash_url="$(uci_get_by_name "$sid" clash_url)"
|
||
local clash_path="$(uci_get_by_name "$sid" clash_path)"
|
||
local clash_user_agent="$(uci_get_by_name "$sid" clash_user_agent clash)"
|
||
local cache_file="$(get_clash_cache_file "$sid")"
|
||
local tmp_file="$TMP_PATH/clash-$sid.download.yaml"
|
||
|
||
if [ -n "$clash_path" ] && [ -s "$clash_path" ]; then
|
||
mkdir -p "$CLASH_CONFIG_DIR"
|
||
cp -f "$clash_path" "$cache_file"
|
||
echolog "Clash 本地配置加载成功:$clash_path"
|
||
return 0
|
||
fi
|
||
|
||
[ -n "$clash_url" ] || {
|
||
echolog "Clash 节点未配置订阅 URL/本地配置,无法启动。"
|
||
return 1
|
||
}
|
||
|
||
mkdir -p "$CLASH_CONFIG_DIR"
|
||
if curl -fsSL --connect-timeout 15 --retry 2 --insecure -A "$clash_user_agent" "$clash_url" -o "$tmp_file"; then
|
||
if /usr/bin/lua "$CLASH_YAML_HELPER" validate "$tmp_file" >/dev/null 2>&1; then
|
||
mv -f "$tmp_file" "$cache_file"
|
||
echolog "Clash 配置下载成功:$clash_url"
|
||
return 0
|
||
else
|
||
echolog "Clash 配置校验失败:$clash_url"
|
||
rm -f "$tmp_file"
|
||
fi
|
||
else
|
||
echolog "Clash 配置下载失败:$clash_url"
|
||
fi
|
||
|
||
if [ -s "$cache_file" ]; then
|
||
echolog "使用缓存的 Clash 配置继续启动。"
|
||
return 0
|
||
fi
|
||
|
||
return 1
|
||
}
|
||
|
||
clash_cache() {
|
||
local sid="$2"
|
||
[ -n "$sid" ] || sid="$1"
|
||
[ -n "$sid" ] || return 1
|
||
[ "$(uci_get_by_name "$sid" type)" = "clash" ] || return 1
|
||
mkdir -p "$TMP_PATH" "$CLASH_CONFIG_DIR"
|
||
download_clash_config "$sid"
|
||
}
|
||
|
||
filter_clash_yaml_proxies() {
|
||
local yaml_file="$1"
|
||
local filter_words="$(uci_get_by_type server_subscribe filter_words '过期时间/剩余流量')"
|
||
local names_file="$TMP_PATH/clash-proxy-names.$$"
|
||
local removed_file="$TMP_PATH/clash-proxy-removed.$$"
|
||
local removed_count=0
|
||
|
||
[ -n "$yaml_file" ] && [ -s "$yaml_file" ] || return 0
|
||
[ -n "$filter_words" ] || return 0
|
||
|
||
if ! /usr/bin/lua "$CLASH_YAML_HELPER" validate "$yaml_file" >/dev/null 2>&1; then
|
||
return 0
|
||
fi
|
||
|
||
removed_count="$(/usr/bin/lua "$CLASH_YAML_HELPER" filter "$yaml_file" "$filter_words" 2>/dev/null || echo 0)"
|
||
if [ -n "$removed_count" ] && [ "$removed_count" -gt 0 ] 2>/dev/null; then
|
||
echolog "Clash YAML 共过滤节点数量: $removed_count"
|
||
fi
|
||
|
||
rm -f "$names_file" "$removed_file"
|
||
}
|
||
|
||
prepare_clash_runtime_config() {
|
||
local sid="$1"
|
||
local local_port="$2"
|
||
local socks_port="$3"
|
||
local cache_file="$(get_clash_cache_file "$sid")"
|
||
local workdir="$(get_clash_workdir "$sid")"
|
||
local raw_file="$workdir/raw.yaml"
|
||
local overlay_file="$workdir/overlay.yaml"
|
||
local runtime_file="$workdir/config.yaml"
|
||
local state_file="$(get_clash_state_file "$sid")"
|
||
local merge_stats=""
|
||
local filled_groups=0
|
||
local stripped_script_rules=0
|
||
local clash_secret="$(get_clash_api_secret "$sid")"
|
||
local dns_ipv4_only="$(get_filter_aaaa)"
|
||
local dns_mode="$(uci_get_by_type global pdnsd_enable 0)"
|
||
|
||
[ -s "$cache_file" ] || return 1
|
||
|
||
mkdir -p "$workdir" "$CLASH_CONFIG_DIR"
|
||
cp -f "$cache_file" "$raw_file"
|
||
filter_clash_yaml_proxies "$raw_file"
|
||
|
||
cat >"$overlay_file" <<-EOF
|
||
redir-port: $local_port
|
||
tproxy-port: $local_port
|
||
external-controller: 127.0.0.1:$CLASH_API_PORT
|
||
secret: $clash_secret
|
||
allow-lan: true
|
||
bind-address: 0.0.0.0
|
||
routing-mark: $CLASH_OUTBOUND_MARK
|
||
profile:
|
||
store-selected: true
|
||
tun:
|
||
enable: false
|
||
dns:
|
||
enable: $( [ "$dns_mode" = "7" ] && echo "true" || echo "false" )
|
||
enhanced-mode: redir-host
|
||
listen: 127.0.0.1:$dns_port
|
||
ipv6: $( [ "$dns_ipv4_only" = "1" ] && echo "false" || echo "true" )
|
||
EOF
|
||
|
||
if [ -n "$socks_port" ] && [ "$socks_port" != "0" ]; then
|
||
echo "socks-port: $socks_port" >>"$overlay_file"
|
||
fi
|
||
|
||
merge_stats="$(/usr/bin/lua "$CLASH_YAML_HELPER" merge "$raw_file" "$overlay_file" "$runtime_file" 2>/dev/null)" || return 1
|
||
filled_groups="$(printf '%s\n' "$merge_stats" | sed -n 's/.*filled_groups=\([0-9][0-9]*\).*/\1/p' | tail -n1)"
|
||
stripped_script_rules="$(printf '%s\n' "$merge_stats" | sed -n 's/.*stripped_script_rules=\([0-9][0-9]*\).*/\1/p' | tail -n1)"
|
||
[ -n "$filled_groups" ] || filled_groups=0
|
||
[ -n "$stripped_script_rules" ] || stripped_script_rules=0
|
||
if [ "$filled_groups" -gt 0 ] 2>/dev/null; then
|
||
echolog "Clash YAML 兼容修复:共为 $filled_groups 个空代理组回填 DIRECT。"
|
||
fi
|
||
if [ "$stripped_script_rules" -gt 0 ] 2>/dev/null; then
|
||
echolog "Clash YAML 兼容修复:共移除 $stripped_script_rules 条不兼容的 SCRIPT 规则。"
|
||
fi
|
||
local client_policy_stats=""
|
||
local client_rules=0
|
||
client_policy_stats="$(/usr/bin/lua "$CLASH_YAML_HELPER" append_client_policy_rules "$runtime_file" "$sid" 2>/dev/null)" || true
|
||
client_rules="$(printf '%s\n' "$client_policy_stats" | sed -n 's/.*client_rules=\([0-9][0-9]*\).*/\1/p' | tail -n1)"
|
||
[ -n "$client_rules" ] || client_rules=0
|
||
if [ "$client_rules" -gt 0 ] 2>/dev/null; then
|
||
echolog "Clash YAML 自定义客户端代理规则已注入:$client_rules 条。"
|
||
fi
|
||
|
||
# Persist Mihomo's store-selected cache across service restarts.
|
||
if [ -f "$workdir/cache.db" ] && [ ! -L "$workdir/cache.db" ]; then
|
||
if [ ! -e "$state_file" ]; then
|
||
mv -f "$workdir/cache.db" "$state_file"
|
||
else
|
||
rm -f "$workdir/cache.db"
|
||
fi
|
||
fi
|
||
rm -f "$workdir/cache.db"
|
||
ln -sf "$state_file" "$workdir/cache.db"
|
||
|
||
# Reuse existing local geodata files so Mihomo does not have to fetch them
|
||
# during startup in environments where DNS or outbound access is not ready yet.
|
||
link_mihomo_geodata "$workdir"
|
||
}
|
||
|
||
resolve_global_clash_socks_port() {
|
||
local socks_enabled=$(uci_get_by_type socks5_proxy enabled 0)
|
||
local socks_server=$(uci_get_by_type socks5_proxy server nil)
|
||
local socks_port=$(uci_get_by_type socks5_proxy local_port 1080)
|
||
local http_enabled=$(uci_get_by_type http_proxy enabled 0)
|
||
local http_server=$(uci_get_by_type http_proxy server nil)
|
||
local http_port="$socks_port"
|
||
local resolved_port=""
|
||
|
||
[ "$socks_server" = "same" ] && socks_server="$GLOBAL_SERVER"
|
||
[ "$http_server" = "same" ] && http_server="$GLOBAL_SERVER"
|
||
|
||
if [ "$socks_enabled" = "1" ] && [ "$socks_server" = "$GLOBAL_SERVER" ]; then
|
||
resolved_port="$socks_port"
|
||
fi
|
||
|
||
if [ "$http_enabled" = "1" ] && [ "$http_server" = "$GLOBAL_SERVER" ]; then
|
||
if [ -n "$resolved_port" ] && [ "$resolved_port" != "$http_port" ]; then
|
||
echolog "Global_HTTP: Mihomo/Clash 仅支持单个 socks-port,HTTP 代理将复用全局 SOCKS5 端口 $resolved_port。"
|
||
else
|
||
resolved_port="$http_port"
|
||
fi
|
||
fi
|
||
|
||
echo "$resolved_port"
|
||
}
|
||
|
||
supports_builtin_socks() {
|
||
case "$1" in
|
||
ss|clash|tuic|v2ray)
|
||
return 0
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
supports_builtin_http() {
|
||
case "$1" in
|
||
clash)
|
||
return 0
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
get_host_ip() {
|
||
local host=$(uci_get_by_name $1 server)
|
||
[ -n "$host" ] || {
|
||
echo ""
|
||
return
|
||
}
|
||
host="${host#\[}"
|
||
host="${host%\]}"
|
||
local ip=""
|
||
if echo "$host" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then
|
||
ip="$host"
|
||
elif ! echo "$host" | grep -q ':'; then
|
||
ip=$(resolveip -4 -t 3 "$host" | awk 'NR==1{print}')
|
||
[ -z "$ip" ] && ip=$(curl -sSL "http://119.29.29.29/d?dn=$host" | awk -F ';' '{print $1}')
|
||
fi
|
||
[ -z "$ip" ] || uci_set_by_name $1 ip $ip
|
||
[ -n "$ip" ] || ip=""
|
||
|
||
local chinadns="$(uci_get_by_type global chinadns_forward)"
|
||
if [ -n "$chinadns" ] && [ "$ip" != "$host" ]; then
|
||
grep -q "$host" "$TMP_DNSMASQ_PATH/chinadns_fixed_server.conf" 2>"/dev/null" || \
|
||
echo -e "address=/$host/$ip" >> "$TMP_DNSMASQ_PATH/chinadns_fixed_server.conf"
|
||
fi
|
||
|
||
echo $ip
|
||
}
|
||
|
||
clean_log() {
|
||
local logsnum=$(cat $LOG_FILE 2>/dev/null | wc -l)
|
||
[ "$logsnum" -gt 1000 ] && {
|
||
echo "$(date "+%Y-%m-%d %H:%M:%S") 日志文件过长,清空处理!" >$LOG_FILE
|
||
}
|
||
}
|
||
|
||
echolog() {
|
||
local d="$(date "+%Y-%m-%d %H:%M:%S")"
|
||
echo -e "$d: $*" >>$LOG_FILE
|
||
}
|
||
|
||
add_cron() {
|
||
touch $CRON_FILE
|
||
sed -i '/ssrplus.log/d' $CRON_FILE
|
||
if [ "$(uci_get_by_type server_subscribe auto_update 0)" -eq 1 ]; then
|
||
if [ "$(uci_get_by_type server_subscribe config_auto_update_mode 0)" = "1" ]; then
|
||
echo "* * * * * /usr/share/shadowsocksr/ssrplusupdate.sh loop >>$LOG_FILE 2>&1" >>$CRON_FILE
|
||
else
|
||
echo "$(uci_get_by_type server_subscribe auto_update_min_time) $(uci_get_by_type server_subscribe auto_update_day_time) * * $(uci_get_by_type server_subscribe auto_update_week_time) /usr/share/shadowsocksr/ssrplusupdate.sh >$LOG_FILE" >>$CRON_FILE
|
||
fi
|
||
fi
|
||
crontab $CRON_FILE
|
||
}
|
||
|
||
del_cron() {
|
||
touch $CRON_FILE
|
||
sed -i '/ssrplus.log/d' $CRON_FILE
|
||
crontab $CRON_FILE
|
||
clean_log
|
||
}
|
||
|
||
set_lock() {
|
||
exec 1000>"$LOCK_FILE"
|
||
flock -xn 1000
|
||
}
|
||
|
||
unset_lock() {
|
||
flock -u 1000
|
||
rm -rf "$LOCK_FILE"
|
||
}
|
||
|
||
unlock() {
|
||
failcount=1
|
||
while [ "$failcount" -le 10 ]; do
|
||
if [ -f "$LOCK_FILE" ]; then
|
||
let "failcount++"
|
||
sleep 1s
|
||
[ "$failcount" -ge 10 ] && unset_lock
|
||
else
|
||
break
|
||
fi
|
||
done
|
||
}
|
||
|
||
_exit() {
|
||
local rc=$1
|
||
unset_lock
|
||
exit ${rc}
|
||
}
|
||
|
||
first_type() {
|
||
local candidate
|
||
for candidate in "/bin/${1}" "/usr/bin/${1}" "/usr/libexec/${1}" "${TMP_BIN_PATH}/${1}"; do
|
||
[ -x "$candidate" ] && {
|
||
echo "$candidate"
|
||
return 0
|
||
}
|
||
done
|
||
|
||
command -v "${1}" 2>/dev/null | head -n1
|
||
}
|
||
|
||
is_finded() {
|
||
[ -n "$(first_type "$1")" ]
|
||
}
|
||
|
||
has_ss_rust_client_binary() {
|
||
[ -n "$(first_type sslocal)" ]
|
||
}
|
||
|
||
has_ss_rust_server_binary() {
|
||
[ -n "$(first_type ssserver)" ]
|
||
}
|
||
|
||
has_mihomo_binary() {
|
||
local mihomo_bin="$(first_type mihomo)"
|
||
[ -x "$mihomo_bin" ]
|
||
}
|
||
|
||
use_mihomo_for_ss_rust_client() {
|
||
has_ss_rust_client_binary && return 1
|
||
has_mihomo_binary
|
||
}
|
||
|
||
use_mihomo_for_ss_rust_server() {
|
||
has_ss_rust_server_binary && return 1
|
||
has_mihomo_binary
|
||
}
|
||
|
||
ln_start_bin() {
|
||
local file_func=${1}
|
||
local ln_name=${2}
|
||
shift 2
|
||
if [ "${file_func%%/*}" != "${file_func}" ]; then
|
||
local ln_path="${TMP_BIN_PATH}/${ln_name}"
|
||
local src_real="$(readlink -f "${file_func}" 2>/dev/null || echo "${file_func}")"
|
||
local dst_real="$(readlink -f "${ln_path}" 2>/dev/null || true)"
|
||
if [ ! -L "${ln_path}" ] || [ "${dst_real}" != "${src_real}" ]; then
|
||
rm -f "${ln_path}"
|
||
ln -s "${file_func}" "${ln_path}" >/dev/null 2>&1
|
||
fi
|
||
file_func="${ln_path}"
|
||
[ -x "${file_func}" ] || echolog "$(readlink ${file_func}) 没有执行权限,无法启动:${file_func} $*"
|
||
fi
|
||
#echo "${file_func} $*" >&2
|
||
[ -x "${file_func}" ] || {
|
||
echolog "找不到 ${file_func},无法启动..."
|
||
echolog "-----------end------------"
|
||
_exit 2
|
||
}
|
||
ulimit -n 1000000
|
||
${file_func:-echolog " - ${ln_name}"} "$@" >/dev/null 2>&1 &
|
||
}
|
||
|
||
check_run_environment() {
|
||
local dnsmasq_info=$(dnsmasq -v 2>/dev/null)
|
||
local dnsmasq_ver=$(echo "$dnsmasq_info" | sed -n '1s/.*version \([0-9.]*\).*/\1/p')
|
||
|
||
DNSMASQ_IPSET=0; [[ "$dnsmasq_info" == *" ipset"* ]] && DNSMASQ_IPSET=1
|
||
DNSMASQ_NFTSET=0; [[ "$dnsmasq_info" == *" nftset"* ]] && DNSMASQ_NFTSET=1
|
||
HAS_IPT=0; { command -v iptables-legacy || command -v iptables; } >/dev/null && HAS_IPT=1
|
||
HAS_IPSET=$(command -v ipset >/dev/null && echo 1 || echo 0)
|
||
HAS_FW3=$(command -v fw3 >/dev/null && echo 1 || echo 0)
|
||
HAS_FW4=$(command -v fw4 >/dev/null && echo 1 || echo 0)
|
||
HAS_NFT=$(command -v nft >/dev/null && echo 1 || echo 0)
|
||
|
||
# 重置 USE_TABLES
|
||
USE_TABLES=""
|
||
|
||
if [ "$HAS_FW4" -eq 1 ]; then
|
||
if [ "$DNSMASQ_NFTSET" -eq 1 ] && [ "$HAS_NFT" -eq 1 ]; then
|
||
USE_TABLES="nftables"
|
||
else
|
||
echolog "警告:fw4 已检测到,但 nftables 环境不完整。(has_nft:$HAS_NFT/dnsmasq_nftset:$DNSMASQ_NFTSET)"
|
||
fi
|
||
fi
|
||
|
||
if [ -z "$USE_TABLES" ] && [ "$HAS_FW3" -eq 1 ]; then
|
||
if [ "$HAS_IPSET" -eq 1 ] && [ "$HAS_IPT" -eq 1 ] && [ "$DNSMASQ_IPSET" -eq 1 ]; then
|
||
USE_TABLES="iptables"
|
||
else
|
||
echolog "警告:fw3 已检测到,但 iptables 环境不完整。(has_ipset:$HAS_IPSET/has_ipt:$HAS_IPT/dnsmasq_ipset:$DNSMASQ_IPSET)"
|
||
fi
|
||
fi
|
||
|
||
if [ -n "$USE_TABLES" ]; then
|
||
local dep_list
|
||
local file_path="/usr/lib/opkg/info"
|
||
local file_ext=".control"
|
||
[ -d "/lib/apk/packages" ] && { file_path="/lib/apk/packages"; file_ext=".list"; }
|
||
|
||
if [ "$USE_TABLES" = "iptables" ]; then
|
||
dep_list="iptables-mod-tproxy iptables-mod-socket iptables-mod-iprange iptables-mod-conntrack-extra kmod-ipt-nat"
|
||
else
|
||
dep_list="kmod-nft-socket kmod-nft-tproxy kmod-nft-nat"
|
||
local v_num=$(echo "$dnsmasq_ver" | tr -cd '0-9')
|
||
if [ "${v_num:-0}" -lt 290 ]; then
|
||
echolog "提示:Dnsmasq ($dnsmasq_ver) 低于 2.90,建议升级以增强稳定性。"
|
||
fi
|
||
fi
|
||
local pkg
|
||
for pkg in $dep_list; do
|
||
if [ ! -s "${file_path}/${pkg}${file_ext}" ]; then
|
||
echolog "警告:${USE_TABLES} 透明代理缺失基础依赖 ${pkg}!"
|
||
fi
|
||
done
|
||
else
|
||
echolog "警告:不满足任何透明代理系统环境。"
|
||
fi
|
||
}
|
||
|
||
normalize_run_mode() {
|
||
local run_mode="$(uci_get_by_type global run_mode router)"
|
||
|
||
if [ "$run_mode" = "oversea" ]; then
|
||
echolog "提示:Oversea Mode 已移除,自动切换到 IP Route Mode。"
|
||
uci_set_by_type global run_mode router
|
||
run_mode="router"
|
||
fi
|
||
|
||
echo "${run_mode:-router}"
|
||
}
|
||
|
||
normalize_xray_protocol_nodes() {
|
||
local changed=0
|
||
|
||
_migrate_xray_protocol_node() {
|
||
local section="$1"
|
||
local type="$(uci_get_by_name "$section" type)"
|
||
local v2ray_protocol="$(uci_get_by_name "$section" v2ray_protocol)"
|
||
|
||
if [ "$type" = "ss" ] || [ "$type" = "ss-libev" ]; then
|
||
if [ -n "$(first_type mihomo)" ]; then
|
||
uci -q set "$NAME.$section.type=ss"
|
||
changed=1
|
||
elif [ -n "$(first_type sslocal)" ]; then
|
||
uci -q set "$NAME.$section.type=ss-rust"
|
||
changed=1
|
||
elif [ -n "$(first_type xray)" ]; then
|
||
uci -q set "$NAME.$section.type=v2ray"
|
||
uci -q set "$NAME.$section.v2ray_protocol=shadowsocks"
|
||
changed=1
|
||
fi
|
||
elif [ "$type" = "v2ray" ] && [ "$v2ray_protocol" = "shadowsocks" ]; then
|
||
if [ -n "$(first_type mihomo)" ]; then
|
||
uci -q set "$NAME.$section.type=ss"
|
||
uci -q delete "$NAME.$section.v2ray_protocol"
|
||
changed=1
|
||
fi
|
||
fi
|
||
if [ "$type" = "hysteria2" ]; then
|
||
echolog "提示:检测到原生 Hysteria2 节点,自动迁移为 Xray(Hysteria2)。"
|
||
uci -q set "$NAME.$section.type=v2ray"
|
||
uci -q set "$NAME.$section.v2ray_protocol=hysteria2"
|
||
changed=1
|
||
elif [ "$type" = "trojan" ]; then
|
||
echolog "提示:检测到原生 Trojan 节点,自动迁移为 Xray(Trojan)。"
|
||
uci -q set "$NAME.$section.type=v2ray"
|
||
uci -q set "$NAME.$section.v2ray_protocol=trojan"
|
||
changed=1
|
||
fi
|
||
}
|
||
|
||
config_load "$NAME"
|
||
config_foreach _migrate_xray_protocol_node servers
|
||
|
||
local subscribe_sid="$(uci_get_by_cfgid server_subscribe)"
|
||
if [ -n "$subscribe_sid" ]; then
|
||
for key in xray_hy2_type xray_tj_type ss_type; do
|
||
if uci -q get "$NAME.$subscribe_sid.$key" >/dev/null; then
|
||
uci -q delete "$NAME.$subscribe_sid.$key"
|
||
changed=1
|
||
fi
|
||
done
|
||
fi
|
||
|
||
if [ "$changed" = "1" ]; then
|
||
uci commit "$NAME"
|
||
fi
|
||
}
|
||
|
||
add_dns_into_ipset() {
|
||
case "$1" in
|
||
gfw) ipset add gfwlist ${2%:*} 2>/dev/null ;;
|
||
*) ipset add ss_spec_wan_ac ${2%:*} nomatch 2>/dev/null ;;
|
||
esac
|
||
}
|
||
|
||
start_dns() {
|
||
local ssrplus_dns="$(uci_get_by_type global pdnsd_enable 0)"
|
||
local global_server_type="$(uci_get_by_name "$GLOBAL_SERVER" type)"
|
||
local builtin_dns_enabled=0
|
||
local dnsserver="$(uci_get_by_type global tunnel_forward 8.8.4.4:53)"
|
||
if [ "$ssrplus_dns" = "5" ]; then
|
||
ssrplus_dns="$(force_dns2tcp_fallback "旧版 DNSPROXY 模式已移除")" || return 1
|
||
uci_set_by_type global pdnsd_enable "$ssrplus_dns"
|
||
elif [ "$ssrplus_dns" = "6" ]; then
|
||
dnsserver="$(uci_get_by_type global chinadns_ng_tunnel_forward 8.8.4.4:53)"
|
||
fi
|
||
local run_mode="$(normalize_run_mode)"
|
||
|
||
if [ "$ssrplus_dns" = "4" ] && ! is_finded "mosdns"; then
|
||
ssrplus_dns="$(force_dns2tcp_fallback "当前 DNS 模式 MosDNS 不可用")" || return 1
|
||
uci_set_by_type global pdnsd_enable "$ssrplus_dns"
|
||
fi
|
||
|
||
if [ "$ssrplus_dns" = "7" ]; then
|
||
if supports_builtin_dns "$global_server_type"; then
|
||
builtin_dns_enabled=1
|
||
else
|
||
ssrplus_dns="$(force_dns2tcp_fallback "当前主节点不支持模块内置 DNS")" || return 1
|
||
uci_set_by_type global pdnsd_enable "$ssrplus_dns"
|
||
fi
|
||
fi
|
||
|
||
if [ "$builtin_dns_enabled" = "1" ]; then
|
||
pdnsd_enable_flag=7
|
||
elif [ "$ssrplus_dns" != "0" ]; then
|
||
if [ "$HAS_IPSET" -eq 1 ]; then
|
||
if [ -n "$dnsserver" ]; then
|
||
add_dns_into_ipset $run_mode $dnsserver
|
||
fi
|
||
fi
|
||
case "$ssrplus_dns" in
|
||
1)
|
||
start_dns2tcp "$dnsserver"
|
||
pdnsd_enable_flag=1
|
||
;;
|
||
4)
|
||
local filter_aaaa="$(get_filter_aaaa)"
|
||
local mosdns_dnsserver="$(uci_get_by_type global tunnel_forward_mosdns)"
|
||
output=$(for i in $(echo $mosdns_dnsserver | sed "s/,/ /g"); do
|
||
dnsserver=${i%:*}
|
||
dnsserver=${i##*/}
|
||
if [ "$HAS_IPSET" -eq 1 ]; then
|
||
add_dns_into_ipset $run_mode $dnsserver
|
||
fi
|
||
echo " - addr: $i"
|
||
echo " enable_pipeline: true"
|
||
done)
|
||
|
||
awk -v line=14 -v text="$output" 'NR == line+1 {print text} 1' /etc/ssrplus/mosdns-config.yaml | sed "s/DNS_PORT/$dns_port/g" | sed "s/\(concurrent:\).*/\1 $(echo "$mosdns_dnsserver" | sed 's/,/ /g' | wc -w)/g"> $TMP_PATH/mosdns-config.yaml
|
||
if [ "$filter_aaaa" == "0" ]; then
|
||
sed -i "s/DNS_MODE/main_sequence_with_IPv6/g" $TMP_PATH/mosdns-config.yaml
|
||
else
|
||
sed -i "s/DNS_MODE/main_sequence_disable_IPv6/g" $TMP_PATH/mosdns-config.yaml
|
||
fi
|
||
ln_start_bin $(first_type mosdns) mosdns start -c $TMP_PATH/mosdns-config.yaml
|
||
pdnsd_enable_flag=4
|
||
;;
|
||
6)
|
||
local chinadns_ng_proto="$(uci_get_by_type global chinadns_ng_proto)"
|
||
local chinadns_ng_dns=""
|
||
# 遍历每个 DNS 服务器
|
||
IFS=',' # 设置分隔符为逗号
|
||
for chinadns_ng_server in $dnsserver; do
|
||
# 处理单个服务器地址
|
||
local chinadns_ng_ip="${chinadns_ng_server%%:*}"
|
||
local chinadns_ng_port="${chinadns_ng_server##*:}"
|
||
[ "$chinadns_ng_ip" = "$chinadns_ng_port" ] && chinadns_ng_port="53"
|
||
chinadns_ng_tls_port="853"
|
||
# 根据协议类型格式化服务器地址
|
||
case "$chinadns_ng_proto" in
|
||
"none")
|
||
chinadns_ng_server="${chinadns_ng_ip}#${chinadns_ng_port}"
|
||
;;
|
||
"tls")
|
||
chinadns_ng_server="${chinadns_ng_proto}://${chinadns_ng_ip}#${chinadns_ng_tls_port}"
|
||
;;
|
||
*)
|
||
chinadns_ng_server="${chinadns_ng_proto}://${chinadns_ng_ip}#${chinadns_ng_port}"
|
||
;;
|
||
esac
|
||
# 添加到参数列表
|
||
chinadns_ng_dns="${chinadns_ng_dns} -t ${chinadns_ng_server}"
|
||
done
|
||
unset IFS # 恢复默认分隔符
|
||
dnsserver="$chinadns_ng_dns"
|
||
ln_start_bin $(first_type chinadns-ng) chinadns-ng -b 127.0.0.1 -l $tmp_dns_port -l $dns_port -p 3 -d gfw $dnsserver -N --filter-qtype 64,65 -f -r --cache 4096 --cache-stale 86400 --cache-refresh 20
|
||
echolog "ChinaDNS-NG query and cache Started!"
|
||
pdnsd_enable_flag=6
|
||
;;
|
||
esac
|
||
|
||
if [ "$run_mode" = "router" ]; then
|
||
local chinadns="$(uci_get_by_type global chinadns_forward)"
|
||
if [ -n "$chinadns" ]; then
|
||
local wandns="$(ifstatus wan | jsonfilter -e '@["dns-server"][0]' || echo "119.29.29.29")"
|
||
case "$chinadns" in
|
||
"wan") chinadns="$wandns" ;;
|
||
"wan_114") chinadns="$wandns,114.114.114.114" ;;
|
||
esac
|
||
|
||
ln_start_bin $(first_type chinadns-ng) chinadns-ng -l $china_dns_port -4 china -p 3 -c ${chinadns/:/#} -t 127.0.0.1#$dns_port -N -f -r
|
||
|
||
cat <<-EOF >> "$TMP_DNSMASQ_PATH/chinadns_fixed_server.conf"
|
||
no-poll
|
||
no-resolv
|
||
server=127.0.0.1#$china_dns_port
|
||
EOF
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
if [ "$(uci_get_by_type global apple_optimization 1)" == "1" ]; then
|
||
local new_appledns="$(uci_get_by_type global apple_dns)"
|
||
if [ -n "$new_appledns" ]; then
|
||
sed -i 's/[[:space:]]//g' /etc/ssrplus/applechina.conf #去除所有空白字符
|
||
local old_appledns=$(grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' /etc/ssrplus/applechina.conf | sort -u)
|
||
if [ -n "$old_appledns" ] && [ "$old_appledns" != "$new_appledns" ]; then
|
||
sed -i "s,$(printf '%s' "$old_appledns"),$(printf '%s' "$new_appledns"),g" /etc/ssrplus/applechina.conf
|
||
fi
|
||
fi
|
||
cp -f /etc/ssrplus/applechina.conf $TMP_DNSMASQ_PATH/
|
||
fi
|
||
}
|
||
|
||
gen_service_file() { #1-server.type 2-cfgname 3-file_path
|
||
local fastopen
|
||
if [ $(uci_get_by_name $2 fast_open) == "1" ]; then
|
||
fastopen="true"
|
||
else
|
||
fastopen="false"
|
||
fi
|
||
case $1 in
|
||
ssr)
|
||
cat <<-EOF >$3
|
||
{
|
||
"server": "0.0.0.0",
|
||
"server_ipv6": "::",
|
||
"server_port": $(uci_get_by_name $2 server_port),
|
||
"mode": "tcp_and_udp",
|
||
"password": "$(uci_get_by_name $2 password)",
|
||
"timeout": $(uci_get_by_name $2 timeout 60),
|
||
"method": "$(uci_get_by_name $2 encrypt_method)",
|
||
"protocol": "$(uci_get_by_name $2 protocol)",
|
||
"protocol_param": "$(uci_get_by_name $2 protocol_param)",
|
||
"obfs": "$(uci_get_by_name $2 obfs)",
|
||
"obfs_param": "$(uci_get_by_name $2 obfs_param)",
|
||
"fast_open": $fastopen
|
||
}
|
||
EOF
|
||
;;
|
||
ss)
|
||
cat <<-EOF >$3
|
||
{
|
||
"server": "0.0.0.0",
|
||
"server_ipv6": "::",
|
||
"server_port": $(uci_get_by_name $2 server_port),
|
||
"mode": "tcp_and_udp",
|
||
"password": "$(uci_get_by_name $2 password)",
|
||
"timeout": $(uci_get_by_name $2 timeout 60),
|
||
"method": "$(uci_get_by_name $2 encrypt_method_ss)",
|
||
"protocol": "socks",
|
||
"fast_open": $fastopen
|
||
}
|
||
EOF
|
||
;;
|
||
esac
|
||
}
|
||
|
||
get_name() {
|
||
case "$1" in
|
||
ss) echo "ShadowSocks" ;;
|
||
ssr) echo "ShadowsocksR" ;;
|
||
esac
|
||
}
|
||
|
||
gen_config_file() { #server1 type2 code3 local_port4 socks_port5 chain6 threads5
|
||
case "$3" in
|
||
1)
|
||
config_file=$tcp_config_file
|
||
chain_config_file=$(echo ${config_file}|sed 's/ssrplus\//ssrplus\/chain-/')
|
||
;;
|
||
2)
|
||
config_file=$udp_config_file
|
||
chain_config_file=$(echo ${config_file}|sed 's/ssrplus\//ssrplus\/chain-/')
|
||
;;
|
||
3)
|
||
if [ -n "$tmp_local_port" ]; then
|
||
local tmp_port=$tmp_local_port
|
||
else
|
||
local tmp_port=$tmp_shunt_local_port
|
||
fi
|
||
config_file=$shunt_config_file
|
||
chain_config_file=$(echo ${config_file}|sed 's/ssrplus\//ssrplus\/chain-/')
|
||
;;
|
||
4)
|
||
local ss_protocol="socks"
|
||
config_file=$local_config_file
|
||
chain_config_file=$(echo ${config_file}|sed 's/ssrplus\//ssrplus\/chain-/')
|
||
;;
|
||
esac
|
||
case "$2" in
|
||
ss | ssr)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 $mode $4 ${ss_protocol:-redir} >$config_file
|
||
if [ "$3" == "3" ]; then
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 $mode $tmp_port socks >$shunt_dns_config_file
|
||
fi
|
||
;;
|
||
v2ray)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 $mode $4 $5 >$config_file
|
||
;;
|
||
trojan)
|
||
case "$3" in
|
||
1)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 nat $4 >$config_file
|
||
;;
|
||
2)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 client $4 >$config_file
|
||
;;
|
||
3)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 nat $4 >$config_file
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 client $tmp_port >$shunt_dns_config_file
|
||
;;
|
||
4)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 client $4 >$config_file
|
||
;;
|
||
esac
|
||
;;
|
||
naiveproxy)
|
||
case "$3" in
|
||
1)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 redir $4 >$config_file
|
||
;;
|
||
3)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 redir $4 >$config_file
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 socks $tmp_port >$shunt_dns_config_file
|
||
;;
|
||
4)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 socks $4 >$config_file
|
||
;;
|
||
esac
|
||
;;
|
||
hysteria2)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 $mode $4 $5 >$config_file
|
||
;;
|
||
tuic)
|
||
case "$3" in
|
||
1|2|4)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 $mode $4 >$config_file
|
||
;;
|
||
3)
|
||
[ -z "$6" ] && lua /usr/share/shadowsocksr/gen_config.lua $1 $mode $4 >$shunt_dns_config_file || lua /usr/share/shadowsocksr/gen_config.lua $1 $mode $4 >$config_file
|
||
;;
|
||
esac
|
||
;;
|
||
shadowtls)
|
||
case "$3" in
|
||
1|2|4)
|
||
[ -z "$6" ] && lua /usr/share/shadowsocksr/gen_config.lua $1 $type $4 >$chain_config_file || lua /usr/share/shadowsocksr/gen_config.lua $1 $mode $4 $5 $6 >$config_file
|
||
;;
|
||
3)
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 $type $4 >$chain_config_file
|
||
lua /usr/share/shadowsocksr/gen_config.lua $1 $mode $4 $5 $6 >$config_file
|
||
;;
|
||
esac
|
||
;;
|
||
esac
|
||
sed -i 's/\\//g' $TMP_PATH/*-ssr-*.json #>/dev/null > 2>&1
|
||
}
|
||
|
||
start_udp() {
|
||
local udp_relay_server_type=$(uci_get_by_name $UDP_RELAY_SERVER type)
|
||
local threads=$(get_configured_threads)
|
||
local type=$udp_relay_server_type
|
||
if [ "$udp_relay_server_type" = "ss-rust" ]; then
|
||
type="ss"
|
||
fi
|
||
redir_udp=1
|
||
case "$type" in
|
||
ss | ssr)
|
||
if [ "$udp_relay_server_type" = "ss" ]; then
|
||
redir_udp=0
|
||
ARG_UDP=""
|
||
echolog "UDP TPROXY Relay:Mihomo 单节点由主实例处理。"
|
||
elif [ "$udp_relay_server_type" = "ss-rust" ] && use_mihomo_for_ss_rust_client; then
|
||
redir_udp=0
|
||
ARG_UDP=""
|
||
echolog "UDP TPROXY Relay:ss-rust 主程序不存在,已回退到 Mihomo 主实例处理。"
|
||
else
|
||
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port
|
||
if [ "$udp_relay_server_type" = "ssr" ]; then
|
||
ss_program="$(first_type ${type}-redir)"
|
||
elif [ "$udp_relay_server_type" = "ss-rust" ] || [ "$udp_relay_server_type" = "ss" ]; then
|
||
ss_program="$(first_type ${type}local)"
|
||
fi
|
||
echolog "$(get_name $type) program is: $ss_program"
|
||
old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-redir" 2>/dev/null)
|
||
if [ "$old_ss_program" != "$ss_program" ]; then
|
||
rm -rf "$TMP_PATH/bin/${type}-redir"
|
||
fi
|
||
ln_start_bin $ss_program ${type}-redir -c $udp_config_file
|
||
echolog "UDP TPROXY Relay:$(get_name $type) Started!"
|
||
fi
|
||
;;
|
||
v2ray)
|
||
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_port
|
||
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
|
||
echolog "UDP TPROXY Relay:$($(first_type xray) version | head -1) Started!"
|
||
;;
|
||
trojan) #client
|
||
gen_config_file $UDP_RELAY_SERVER $type 2 $tmp_udp_local_port
|
||
ln_start_bin $(first_type trojan) $type --config $udp_config_file
|
||
ln_start_bin $(first_type ipt2socks) ipt2socks -U -b 0.0.0.0 -4 -s 127.0.0.1 -p $tmp_udp_local_port -l $tmp_udp_port
|
||
echolog "UDP TPROXY Relay:$($(first_type trojan) --version 2>&1 | head -1) Started!"
|
||
;;
|
||
naiveproxy)
|
||
echolog "NaïveProxy UDP TPROXY Relay not supported!"
|
||
redir_udp=0
|
||
ARG_UDP=""
|
||
;;
|
||
tuic)
|
||
# TUIC now uses Mihomo directly and should follow the native path.
|
||
redir_udp=0
|
||
ARG_UDP=""
|
||
echolog "TUIC UDP relay is handled by the main Mihomo instance."
|
||
;;
|
||
shadowtls)
|
||
gen_config_file $UDP_RELAY_SERVER $type 2 ${tmp_udp_local_port}
|
||
gen_config_file $UDP_RELAY_SERVER $type 2 ${tmp_udp_local_port} 0 chain
|
||
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file
|
||
local chain_type=$(uci_get_by_name $UDP_RELAY_SERVER chain_type)
|
||
case ${chain_type} in
|
||
vmess)
|
||
ln_start_bin $(first_type xray) v2ray run -c $udp_config_file
|
||
echolog "UDP TPROXY Relay:shadow-tls chain-to $($(first_type xray) --version) Started!"
|
||
;;
|
||
sslocal)
|
||
ln_start_bin $(first_type sslocal) sslocal -c $udp_config_file
|
||
echolog "UDP TPROXY Relay:shadow-tls chain-to $($(first_type sslocal) --version) Started!"
|
||
;;
|
||
esac
|
||
;;
|
||
socks5)
|
||
if [ "$(uci_get_by_name $UDP_RELAY_SERVER auth_enable 0)" == "1" ]; then
|
||
local auth="-a $(uci_get_by_name $UDP_RELAY_SERVER username) -k $(uci_get_by_name $UDP_RELAY_SERVER password)"
|
||
fi
|
||
for i in $(seq 1 $threads); do
|
||
ln_start_bin $(first_type ipt2socks) ipt2socks -U -r -b 0.0.0.0 -4 \
|
||
-s $(uci_get_by_name $UDP_RELAY_SERVER server) \
|
||
-p $(uci_get_by_name $UDP_RELAY_SERVER server_port) \
|
||
-l $tmp_udp_port $auth
|
||
done
|
||
echolog "UDP TPROXY Relay:Socks5 via IPT2Socks $threads Threads Started!"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
start_local() {
|
||
[ "$LOCAL_SERVER" = "nil" ] && return 1
|
||
local local_port="${1:-$(uci_get_by_type socks5_proxy local_port)}"
|
||
local local_server_type=$(uci_get_by_name $LOCAL_SERVER type)
|
||
local probe_instance_key="$(printf '%s' "${SSR_SWITCH_PROBE_INSTANCE_KEY:-}" | tr -cd '0-9A-Za-z._-')"
|
||
local type=$local_server_type
|
||
if [ "$local_server_type" = "ss-rust" ]; then
|
||
type="ss"
|
||
fi
|
||
case "$type" in
|
||
ss | ssr)
|
||
if [ "$local_server_type" = "ssr" ]; then
|
||
gen_config_file $LOCAL_SERVER $type 4 $local_port
|
||
ss_program="$(first_type ${type}-local)"
|
||
echolog "$(get_name $type) program is: $ss_program"
|
||
old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-local" 2>/dev/null)
|
||
if [ "$old_ss_program" != "$ss_program" ]; then
|
||
rm -rf "$TMP_PATH/bin/${type}-local"
|
||
fi
|
||
ln_start_bin $ss_program ${type}-local -c $local_config_file
|
||
echolog "Global_Socks5:$(get_name $type) Started!"
|
||
elif [ "$local_server_type" = "ss" ] || { [ "$local_server_type" = "ss-rust" ] && use_mihomo_for_ss_rust_client; }; then
|
||
local mihomo_bin="$(first_type mihomo)"
|
||
local instance_key="${LOCAL_SERVER}-local"
|
||
[ -n "$probe_instance_key" ] && instance_key="$probe_instance_key"
|
||
[ -x "$mihomo_bin" ] || {
|
||
echolog "Global_Socks5:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。"
|
||
return 1
|
||
}
|
||
prepare_ss_mihomo_runtime_config "$LOCAL_SERVER" "$local_port" "$local_port" "$instance_key" "socks" || return 1
|
||
local ss_workdir="$(get_ss_mihomo_workdir "$instance_key")"
|
||
ln_start_bin "$mihomo_bin" ss-local -d "$ss_workdir" -f "$ss_workdir/config.yaml"
|
||
if [ "$local_server_type" = "ss-rust" ]; then
|
||
echolog "Global_Socks5:ss-rust 主程序不存在,已回退到 Mihomo (Shadowsocks) 启动。"
|
||
else
|
||
echolog "Global_Socks5:Mihomo (Shadowsocks) Started!"
|
||
fi
|
||
else
|
||
gen_config_file $LOCAL_SERVER $type 4 $local_port
|
||
ss_program="$(first_type sslocal)"
|
||
echolog "ShadowSocks program is: $ss_program"
|
||
old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-local" 2>/dev/null)
|
||
if [ "$old_ss_program" != "$ss_program" ]; then
|
||
rm -rf "$TMP_PATH/bin/${type}-local"
|
||
fi
|
||
ln_start_bin $ss_program ${type}-local -c $local_config_file
|
||
echolog "Global_Socks5:Shadowsocks-rust Started!"
|
||
fi
|
||
;;
|
||
v2ray)
|
||
if [ "$_local" == "2" ]; then
|
||
gen_config_file $LOCAL_SERVER $type 4 0 $local_port
|
||
ln_start_bin $(first_type xray) v2ray run -c $local_config_file
|
||
fi
|
||
echolog "Global_Socks5:$($(first_type xray) version | head -1) Started!"
|
||
;;
|
||
trojan) #client
|
||
gen_config_file $LOCAL_SERVER $type 4 $local_port
|
||
ln_start_bin $(first_type trojan) $type --config $local_config_file
|
||
echolog "Global_Socks5:$($(first_type trojan) --version 2>&1 | head -1) Started!"
|
||
;;
|
||
naiveproxy)
|
||
gen_config_file $LOCAL_SERVER $type 4 $local_port
|
||
ln_start_bin $(first_type naive) naive --config $local_config_file
|
||
echolog "Global_Socks5:$($(first_type naive) --version | head -1) Started!"
|
||
;;
|
||
tuic)
|
||
local mihomo_bin="$(first_type mihomo)"
|
||
local instance_key="${LOCAL_SERVER}-local"
|
||
[ -n "$probe_instance_key" ] && instance_key="$probe_instance_key"
|
||
[ -x "$mihomo_bin" ] || {
|
||
echolog "Global_Socks5:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。"
|
||
return 1
|
||
}
|
||
prepare_tuic_runtime_config "$LOCAL_SERVER" "$local_port" "$local_port" "$instance_key" "socks" || return 1
|
||
local tuic_workdir="$(get_tuic_workdir "$instance_key")"
|
||
ln_start_bin "$mihomo_bin" tuic-local -d "$tuic_workdir" -f "$tuic_workdir/config.yaml"
|
||
echolog "Global_Socks5:Mihomo (TUIC) Started!"
|
||
;;
|
||
shadowtls)
|
||
#respective config for global socks and main node
|
||
if [ "$_local" == "2" ]; then
|
||
gen_config_file $LOCAL_SERVER $type 4 "10${tmp_tcp_local_port}"
|
||
gen_config_file $LOCAL_SERVER $type 4 0 $local_port chain/"10${tmp_tcp_local_port}"
|
||
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_local_config_file
|
||
local chain_type=$(uci_get_by_name $LOCAL_SERVER chain_type)
|
||
case ${chain_type} in
|
||
vmess)
|
||
ln_start_bin $(first_type xray) v2ray run -c $local_config_file
|
||
echolog "Global Socks5 Proxy:shadow-tls chain-to$($(first_type xray) --version) Started!"
|
||
;;
|
||
sslocal)
|
||
ln_start_bin $(first_type sslocal) sslocal -c $local_config_file
|
||
echolog "Global Socks5 Proxy:shadow-tls chain-to$($(first_type sslocal) --version) Started!"
|
||
;;
|
||
esac
|
||
fi
|
||
;;
|
||
clash)
|
||
echolog "Global_Socks5:Clash total node is only supported when using Same as Global Server."
|
||
return 1
|
||
;;
|
||
socks5)
|
||
echolog "Global_Socks5:Socks5 transparent-only node cannot be used as generic Socks outbound."
|
||
return 1
|
||
;;
|
||
*)
|
||
local listenip='-i 0.0.0.0'
|
||
[ -e /proc/sys/net/ipv6 ] && listenip='-i ::'
|
||
ln_start_bin $(first_type microsocks) microsocks $listenip -p $local_port tcp-udp-ssr-local
|
||
echolog "Global_Socks5:$type Started!"
|
||
;;
|
||
esac
|
||
local_enable=1
|
||
return 0
|
||
}
|
||
|
||
start_local_with_port() {
|
||
start_local "$1"
|
||
}
|
||
|
||
gen_3proxy_config() {
|
||
local cfg_file="$1"
|
||
local listen_port="$2"
|
||
local relay_socks_port="$3"
|
||
local auth_mode="$4"
|
||
local auth_user="$5"
|
||
local auth_pass="$6"
|
||
|
||
cat <<-EOF >"$cfg_file"
|
||
daemon
|
||
pidfile $TMP_PATH/3proxy.pid
|
||
nserver 8.8.8.8
|
||
nscache 65536
|
||
timeouts 1 5 30 60 180 1800 15 60
|
||
authcache ip 60
|
||
internal 0.0.0.0
|
||
external 0.0.0.0
|
||
flush
|
||
EOF
|
||
|
||
if [ "$auth_mode" = "password" ] && [ -n "$auth_user" ]; then
|
||
cat <<-EOF >>"$cfg_file"
|
||
auth strong
|
||
users $auth_user:CL:$auth_pass
|
||
allow $auth_user
|
||
EOF
|
||
else
|
||
cat <<-'EOF' >>"$cfg_file"
|
||
auth none
|
||
allow *
|
||
EOF
|
||
fi
|
||
|
||
cat <<-EOF >>"$cfg_file"
|
||
parent 1000 socks5 127.0.0.1 $relay_socks_port
|
||
proxy -n -a -p$listen_port
|
||
flush
|
||
EOF
|
||
}
|
||
|
||
start_http_proxy() {
|
||
[ "$HTTP_SERVER" = "nil" ] && return 1
|
||
|
||
local http_local_port=$(uci_get_by_type http_proxy local_port 3128)
|
||
local http_auth_mode=$(uci_get_by_type http_proxy http_auth none)
|
||
local http_user=$(uci_get_by_type http_proxy http_user)
|
||
local http_pass=$(uci_get_by_type http_proxy http_pass)
|
||
local http_server_type=$(uci_get_by_name $HTTP_SERVER type)
|
||
local need_local_start=1
|
||
local prev_local_server="$LOCAL_SERVER"
|
||
local prev_local_config_file="$local_config_file"
|
||
local prev_local_flag="$local_enable"
|
||
local prev_local_mode="$_local"
|
||
local http_config_file="$TMP_PATH/3proxy-ssr-http.cfg"
|
||
local global_socks_enabled=$(uci_get_by_type socks5_proxy enabled 0)
|
||
local global_socks_port=$(uci_get_by_type socks5_proxy local_port 1080)
|
||
local global_socks_server=$(uci_get_by_type socks5_proxy server nil)
|
||
local resolved_socks_server="$global_socks_server"
|
||
local http_socks_port="$global_socks_port"
|
||
|
||
if [ -z "$http_socks_port" ] || [ "$http_socks_port" = "0" ]; then
|
||
http_socks_port=1080
|
||
fi
|
||
|
||
[ "$http_local_port" = "$http_socks_port" ] && {
|
||
echolog "Global_HTTP: HTTP 监听端口与上游 SOCKS5 端口不能相同。"
|
||
return 1
|
||
}
|
||
|
||
if [ "$resolved_socks_server" = "same" ]; then
|
||
resolved_socks_server="$GLOBAL_SERVER"
|
||
fi
|
||
|
||
if [ "$global_socks_enabled" = "1" ] && [ "$http_socks_port" = "$global_socks_port" ] && [ "$resolved_socks_server" != "$HTTP_SERVER" ]; then
|
||
echolog "Global_HTTP: Relay SOCKS5 Port 已被全局 SOCKS5 代理占用,请改用其他端口。"
|
||
return 1
|
||
fi
|
||
|
||
if [ "$http_auth_mode" = "password" ] && [ -z "$http_user" ]; then
|
||
echolog "Global_HTTP: HTTP 认证模式为 password 时必须填写用户名。"
|
||
return 1
|
||
fi
|
||
|
||
case "$http_server_type" in
|
||
socks5)
|
||
echolog "Global_HTTP: 当前节点类型不支持作为 3proxy 的上游 SOCKS5 出口。"
|
||
return 1
|
||
;;
|
||
esac
|
||
|
||
if [ "$http_server_type" = "clash" ]; then
|
||
if [ "$HTTP_SERVER" != "$GLOBAL_SERVER" ]; then
|
||
echolog "Global_HTTP: Clash 节点仅支持选择 Same as Global Server 进行复用。"
|
||
return 1
|
||
fi
|
||
if [ "$http_socks_port" != "$global_socks_port" ] && [ "$global_socks_enabled" = "1" ] && [ "$resolved_socks_server" = "$HTTP_SERVER" ]; then
|
||
echolog "Global_HTTP: Mihomo/Clash 仅支持单个 socks-port,请让 HTTP 代理与全局 SOCKS5 使用相同的上游 SOCKS5 端口。"
|
||
return 1
|
||
fi
|
||
fi
|
||
|
||
if supports_builtin_http "$http_server_type" && [ "$HTTP_SERVER" = "$GLOBAL_SERVER" ]; then
|
||
need_local_start=0
|
||
elif [ "$global_socks_enabled" = "1" ] && [ "$resolved_socks_server" = "$HTTP_SERVER" ] && [ "$global_socks_port" = "$http_socks_port" ]; then
|
||
need_local_start=0
|
||
else
|
||
LOCAL_SERVER="$HTTP_SERVER"
|
||
local_config_file="$TMP_PATH/tcp-http-ssr-local.json"
|
||
_local="2"
|
||
local_enable=0
|
||
start_local_with_port "$http_socks_port" || {
|
||
LOCAL_SERVER="$prev_local_server"
|
||
local_config_file="$prev_local_config_file"
|
||
local_enable="$prev_local_flag"
|
||
_local="$prev_local_mode"
|
||
return 1
|
||
}
|
||
LOCAL_SERVER="$prev_local_server"
|
||
local_config_file="$prev_local_config_file"
|
||
local_enable="$prev_local_flag"
|
||
_local="$prev_local_mode"
|
||
fi
|
||
|
||
[ "$need_local_start" = "0" ] && echolog "Global_HTTP: Reuse existing local Socks5 upstream on port $http_socks_port."
|
||
|
||
gen_3proxy_config "$http_config_file" "$http_local_port" "$http_socks_port" "$http_auth_mode" "$http_user" "$http_pass"
|
||
ln_start_bin $(first_type 3proxy) 3proxy "$http_config_file"
|
||
echolog "Global_HTTP:3proxy HTTP/HTTPS Proxy Started!"
|
||
http_enable=1
|
||
return 0
|
||
}
|
||
|
||
get_udp_relay_mode() {
|
||
local node="$1"
|
||
local type
|
||
local proto
|
||
local socks_ver
|
||
|
||
type=$(uci_get_by_name "$node" type)
|
||
[ "$type" = "ss-rust" ] && type="ss"
|
||
|
||
case "$type" in
|
||
ss|ssr|hysteria2)
|
||
echo "native"
|
||
;;
|
||
clash|tuic)
|
||
echo "native"
|
||
;;
|
||
trojan|shadowtls)
|
||
echo "split"
|
||
;;
|
||
socks5)
|
||
echo "split"
|
||
;;
|
||
naiveproxy)
|
||
echo "disabled"
|
||
;;
|
||
v2ray)
|
||
proto=$(uci_get_by_name "$node" v2ray_protocol)
|
||
case "$proto" in
|
||
http)
|
||
echo "disabled"
|
||
;;
|
||
socks)
|
||
socks_ver=$(uci_get_by_name "$node" socks_ver 5)
|
||
[ "$socks_ver" = "5" ] && echo "native" || echo "disabled"
|
||
;;
|
||
*)
|
||
echo "native"
|
||
;;
|
||
esac
|
||
;;
|
||
*)
|
||
echo "disabled"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
Start_Run() {
|
||
local threads=$(get_configured_threads)
|
||
local global_server_type=$(uci_get_by_name $GLOBAL_SERVER type)
|
||
if [ "$(uci_get_by_name $GLOBAL_SERVER kcp_enable 0)" == "1" ] && [ "$global_server_type" != "ss" ]; then
|
||
[ ! -f "/usr/bin/kcptun-client" ] && return 1
|
||
local kcp_str=$(/usr/bin/kcptun-client -v | grep kcptun | wc -l)
|
||
[ "0" == "$kcp_str" ] && return 1
|
||
local kcp_server=$(uci_get_by_name $GLOBAL_SERVER server)
|
||
local kcp_port=$(uci_get_by_name $GLOBAL_SERVER kcp_port)
|
||
local server_port=$(uci_get_by_name $GLOBAL_SERVER server_port)
|
||
local password=$(uci_get_by_name $GLOBAL_SERVER kcp_password)
|
||
local kcp_param=$(uci_get_by_name $GLOBAL_SERVER kcp_param)
|
||
[ "$password" != "" ] && password="--key "$password
|
||
service_start /usr/bin/kcptun-client -r $kcp_server:$kcp_port -l :$server_port $password $kcp_param
|
||
kcp_enable_flag=1
|
||
ARG_UDP=""
|
||
fi
|
||
if [ "$_local" == "1" ]; then
|
||
local socks_port=$(uci_get_by_type socks5_proxy local_port)
|
||
tcp_config_file=$TMP_PATH/local-ssr-retcp.json
|
||
[ "$mode" == "tcp,udp" ] && tcp_config_file=$TMP_PATH/local-udp-ssr-retcp.json
|
||
#[ "$mode" == "tcp,udp" ] && {
|
||
# if [ "$USE_TABLES" = "nftables" ]; then
|
||
# # nftables / fw4
|
||
# tcp_config_file=$TMP_PATH/local-nft-ssr-retcp.json
|
||
# elif [ "$USE_TABLES" = "iptables" ]; then
|
||
# # iptables / fw3
|
||
# tcp_config_file=$TMP_PATH/local-udp-ssr-retcp.json
|
||
# fi
|
||
#}
|
||
fi
|
||
local tcp_port=$(get_default_node_local_port)
|
||
local type=$global_server_type
|
||
if [ "$global_server_type" = "ss-rust" ]; then
|
||
type="ss"
|
||
fi
|
||
case "$type" in
|
||
ss | ssr)
|
||
if [ "$global_server_type" = "ssr" ]; then
|
||
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port
|
||
ss_program="$(first_type ${type}-redir)"
|
||
echolog "$(get_name $type) program is: $ss_program"
|
||
old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-redir" 2>/dev/null)
|
||
if [ "$old_ss_program" != "$ss_program" ]; then
|
||
rm -rf "$TMP_PATH/bin/${type}-redir"
|
||
fi
|
||
for i in $(seq 1 $threads); do
|
||
ln_start_bin $ss_program ${type}-redir -c $tcp_config_file
|
||
done
|
||
echolog "Main node:$(get_name $type) $threads Threads Started!"
|
||
elif [ "$global_server_type" = "ss" ] || { [ "$global_server_type" = "ss-rust" ] && use_mihomo_for_ss_rust_client; }; then
|
||
local mihomo_bin="$(first_type mihomo)"
|
||
local ss_socks_port=0
|
||
[ -x "$mihomo_bin" ] || {
|
||
echolog "Main node:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。"
|
||
return 1
|
||
}
|
||
if [ "$_local" = "1" ] && [ "$LOCAL_SERVER" = "$GLOBAL_SERVER" ]; then
|
||
ss_socks_port=$(uci_get_by_type socks5_proxy local_port 1080)
|
||
fi
|
||
prepare_ss_mihomo_runtime_config "$GLOBAL_SERVER" "$tcp_port" "$ss_socks_port" "$GLOBAL_SERVER" "redir" || return 1
|
||
local ss_workdir="$(get_ss_mihomo_workdir "$GLOBAL_SERVER")"
|
||
ln_start_bin "$mihomo_bin" ssr-retcp -d "$ss_workdir" -f "$ss_workdir/config.yaml"
|
||
if [ "$global_server_type" = "ss-rust" ]; then
|
||
echolog "Main node:ss-rust 主程序不存在,已回退到 Mihomo (Shadowsocks) 启动。"
|
||
else
|
||
echolog "Main node:Mihomo (Shadowsocks) Started!"
|
||
fi
|
||
else
|
||
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port
|
||
ss_program="$(first_type sslocal)"
|
||
echolog "ShadowSocks program is: $ss_program"
|
||
old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-redir" 2>/dev/null)
|
||
if [ "$old_ss_program" != "$ss_program" ]; then
|
||
rm -rf "$TMP_PATH/bin/${type}-redir"
|
||
fi
|
||
ln_start_bin $ss_program ${type}-redir -c $tcp_config_file
|
||
echolog "Main node:Shadowsocks-rust Started!"
|
||
fi
|
||
;;
|
||
v2ray)
|
||
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port $socks_port
|
||
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
|
||
echolog "Main node:$($(first_type xray) version | head -1) Started!"
|
||
;;
|
||
trojan)
|
||
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port
|
||
for i in $(seq 1 $threads); do
|
||
ln_start_bin $(first_type $type) $type --config $tcp_config_file
|
||
done
|
||
echolog "Main node:$($(first_type $type) --version 2>&1 | head -1) , $threads Threads Started!"
|
||
;;
|
||
naiveproxy)
|
||
gen_config_file $GLOBAL_SERVER $type 1 $tcp_port
|
||
ln_start_bin $(first_type naive) naive $tcp_config_file
|
||
echolog "Main node:$($(first_type naive) --version 2>&1 | head -1) , $threads Threads Started!"
|
||
;;
|
||
tuic)
|
||
local mihomo_bin="$(first_type mihomo)"
|
||
local tuic_socks_port=0
|
||
[ -x "$mihomo_bin" ] || {
|
||
echolog "Main node:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。"
|
||
return 1
|
||
}
|
||
if [ "$_local" = "1" ] && [ "$LOCAL_SERVER" = "$GLOBAL_SERVER" ]; then
|
||
tuic_socks_port=$(uci_get_by_type socks5_proxy local_port 1080)
|
||
fi
|
||
prepare_tuic_runtime_config "$GLOBAL_SERVER" "$tcp_port" "$tuic_socks_port" "$GLOBAL_SERVER" "redir" || return 1
|
||
local tuic_workdir="$(get_tuic_workdir "$GLOBAL_SERVER")"
|
||
ln_start_bin "$mihomo_bin" ssr-retcp -d "$tuic_workdir" -f "$tuic_workdir/config.yaml"
|
||
echolog "Main node:Mihomo (TUIC) Started!"
|
||
;;
|
||
shadowtls)
|
||
if [ -z "$socks_port" ]; then
|
||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}"
|
||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" 0 chain
|
||
else
|
||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}"
|
||
gen_config_file $GLOBAL_SERVER $type 1 "10${tmp_tcp_local_port}" $socks_port chain
|
||
fi
|
||
local chain_type=$(uci_get_by_name $GLOBAL_SERVER chain_type)
|
||
case ${chain_type} in
|
||
vmess)
|
||
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file
|
||
ln_start_bin $(first_type xray) v2ray run -c $tcp_config_file
|
||
echolog "Mian node:shadow-tls chain-to $($(first_type xray) --version) Started!"
|
||
;;
|
||
sslocal)
|
||
ln_start_bin $(first_type shadow-tls) shadow-tls config --config $chain_config_file
|
||
ln_start_bin $(first_type sslocal) sslocal -c $tcp_config_file
|
||
echolog "Main node:shadow-tls chain-to $($(first_type sslocal) --version) Started!"
|
||
;;
|
||
esac
|
||
;;
|
||
clash)
|
||
local clash_socks_port=0
|
||
local mihomo_bin="$(first_type mihomo)"
|
||
clash_socks_port="$(resolve_global_clash_socks_port)"
|
||
if [ -z "$clash_socks_port" ] && [ "$_local" == "1" ] && [ "$LOCAL_SERVER" = "$GLOBAL_SERVER" ]; then
|
||
clash_socks_port=$(uci_get_by_type socks5_proxy local_port 1080)
|
||
fi
|
||
[ -x "$mihomo_bin" ] || {
|
||
echolog "Main node:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。"
|
||
return 1
|
||
}
|
||
download_clash_config "$GLOBAL_SERVER" || {
|
||
echolog "Main node:Clash 配置不可用,启动中止。"
|
||
return 1
|
||
}
|
||
prepare_clash_runtime_config "$GLOBAL_SERVER" "$tcp_port" "$clash_socks_port" || {
|
||
echolog "Main node:Clash 运行配置生成失败,启动中止。"
|
||
return 1
|
||
}
|
||
local clash_workdir="$(get_clash_workdir "$GLOBAL_SERVER")"
|
||
ln_start_bin "$mihomo_bin" ssr-retcp -d "$clash_workdir" -f "$clash_workdir/config.yaml"
|
||
echolog "Main node:Mihomo (Clash) Started!"
|
||
;;
|
||
socks5)
|
||
local ipt2socks_bin="$(first_type ipt2socks)"
|
||
local auth_opts=""
|
||
[ -x "$ipt2socks_bin" ] || {
|
||
echolog "Main node:Socks5 缺少 ipt2socks,无法启动透明代理。"
|
||
return 1
|
||
}
|
||
if [ "$(uci_get_by_name $GLOBAL_SERVER auth_enable 0)" = "1" ]; then
|
||
auth_opts="-a $(uci_get_by_name $GLOBAL_SERVER username) -k $(uci_get_by_name $GLOBAL_SERVER password)"
|
||
fi
|
||
for i in $(seq 1 $threads); do
|
||
ln_start_bin "$ipt2socks_bin" ipt2socks \
|
||
-T -R -r -b 0.0.0.0 -4 \
|
||
-s "$(uci_get_by_name $GLOBAL_SERVER server)" \
|
||
-p "$(uci_get_by_name $GLOBAL_SERVER server_port)" \
|
||
-l "$tcp_port" $auth_opts
|
||
done
|
||
echolog "Main node:Socks5 via IPT2Socks $threads Threads Started!"
|
||
;;
|
||
esac
|
||
redir_tcp=1
|
||
return 0
|
||
}
|
||
|
||
load_config() {
|
||
if [ -z "$switch_server" ]; then
|
||
GLOBAL_SERVER=$(uci_get_by_type global global_server nil)
|
||
else
|
||
GLOBAL_SERVER=$switch_server
|
||
fi
|
||
if [ "$(uci_get_by_type socks5_proxy enabled 0)" == "1" ]; then
|
||
uci -q set "$NAME.@socks5_proxy[0].server=same"
|
||
else
|
||
uci -q set "$NAME.@socks5_proxy[0].server=nil"
|
||
fi
|
||
if [ "$(uci_get_by_type http_proxy enabled 0)" == "1" ]; then
|
||
uci -q set "$NAME.@http_proxy[0].server=same"
|
||
else
|
||
uci -q set "$NAME.@http_proxy[0].server=nil"
|
||
fi
|
||
uci -q commit "$NAME"
|
||
if [ "$(uci_get_by_type socks5_proxy enabled 0)" == "1" ]; then
|
||
# 只有开启 全局socks 才需要取值
|
||
LOCAL_SERVER=$(uci_get_by_type socks5_proxy server nil)
|
||
else
|
||
# 没有开启 设置为 nil
|
||
LOCAL_SERVER=nil
|
||
fi
|
||
if [ "$(uci_get_by_type http_proxy enabled 0)" == "1" ]; then
|
||
HTTP_SERVER=$(uci_get_by_type http_proxy server nil)
|
||
else
|
||
HTTP_SERVER=nil
|
||
fi
|
||
if [ "$GLOBAL_SERVER" == "nil" ]; then
|
||
[ "$HTTP_SERVER" = "same" ] && HTTP_SERVER=nil
|
||
mode="tcp,udp"
|
||
_local="2"
|
||
local_config_file=$TMP_PATH/tcp-udp-ssr-local.json
|
||
start_local
|
||
return 1
|
||
fi
|
||
UDP_RELAY_SERVER=$GLOBAL_SERVER
|
||
local udp_mode=$(get_udp_relay_mode "$GLOBAL_SERVER")
|
||
tcp_config_file=$TMP_PATH/tcp-only-ssr-retcp.json
|
||
case "$udp_mode" in
|
||
disabled)
|
||
echolog "提示:当前节点类型不提供 UDP 透明代理,也不会启用任何 UDP 规则,仅启用 TCP 透明代理。"
|
||
mode="tcp"
|
||
ARG_UDP=""
|
||
ARG_UDP_RULES="-y"
|
||
udp_config_file=""
|
||
;;
|
||
native)
|
||
mode="tcp,udp"
|
||
tcp_config_file=$TMP_PATH/tcp-udp-ssr-retcp.json
|
||
ARG_UDP="-u"
|
||
ARG_UDP_RULES=""
|
||
;;
|
||
split)
|
||
mode="udp"
|
||
udp_config_file=$TMP_PATH/udp-only-ssr-reudp.json
|
||
ARG_UDP="-U"
|
||
ARG_UDP_RULES=""
|
||
start_udp
|
||
mode="tcp"
|
||
;;
|
||
esac
|
||
case "$LOCAL_SERVER" in
|
||
nil)
|
||
_local="0"
|
||
;;
|
||
"$GLOBAL_SERVER"|same)
|
||
_local="1"
|
||
LOCAL_SERVER=$GLOBAL_SERVER
|
||
local_config_file=$TMP_PATH/tcp-udp-ssr-local.json
|
||
if ! supports_builtin_socks "$(uci_get_by_name $GLOBAL_SERVER type)"; then
|
||
start_local
|
||
fi
|
||
local_enable=0
|
||
;;
|
||
*)
|
||
_local="2"
|
||
local_config_file=$TMP_PATH/tcp-udp-ssr-local.json
|
||
start_local
|
||
;;
|
||
esac
|
||
[ "$HTTP_SERVER" = "same" ] && HTTP_SERVER=$GLOBAL_SERVER
|
||
return 0
|
||
}
|
||
|
||
check_server() {
|
||
ENABLE_SERVER=$(uci_get_by_type global global_server nil)
|
||
if [ "$ENABLE_SERVER" == "nil" ]; then
|
||
return 1
|
||
else
|
||
local STYPE=$(uci_get_by_name $ENABLE_SERVER type nil)
|
||
if [ "$STYPE" == "nil" ]; then
|
||
local CFGID=$(uci_get_by_cfgid servers type nil)
|
||
if [ "$CFGID" == "nil" ]; then
|
||
uci_set_by_type global global_server 'nil'
|
||
else
|
||
uci_set_by_type global global_server $CFGID
|
||
fi
|
||
/etc/init.d/shadowsocksr restart
|
||
fi
|
||
fi
|
||
}
|
||
|
||
start_server() {
|
||
[ "$(uci_get_by_type server_global enable_server 0)" == "0" ] && return 0
|
||
server_service() {
|
||
[ "$(uci_get_by_name $1 enable 0)" == "0" ] && return 1
|
||
let server_count=server_count+1
|
||
if [ "$server_count" == "1" ]; then
|
||
if [ "$USE_TABLES" = "nftables" ]; then
|
||
# nftables / fw4
|
||
if nft list table inet fw4 >/dev/null 2>&1; then
|
||
if ! nft list chain inet fw4 SSR-SERVER-RULE >/dev/null 2>&1; then
|
||
nft add chain inet fw4 SSR-SERVER-RULE 2>/dev/null
|
||
fi
|
||
if ! nft list chain inet fw4 input 2>/dev/null | grep -q "jump SSR-SERVER-RULE"; then
|
||
nft insert rule inet fw4 input jump SSR-SERVER-RULE comment \"SSR Server Input Hook\" 2>/dev/null
|
||
fi
|
||
nft flush chain inet fw4 SSR-SERVER-RULE 2>/dev/null
|
||
fi
|
||
elif [ "$USE_TABLES" = "iptables" ]; then
|
||
# iptables / fw3
|
||
if ! (iptables-save -t filter | grep -q "SSR-SERVER-RULE"); then
|
||
iptables -N SSR-SERVER-RULE
|
||
iptables -t filter -I INPUT -j SSR-SERVER-RULE
|
||
fi
|
||
fi
|
||
fi
|
||
local node_type=$(uci_get_by_name $1 type)
|
||
local type=$node_type
|
||
if [ "$node_type" = "ss-rust" ]; then
|
||
type="ss"
|
||
fi
|
||
case "$type" in
|
||
ss | ssr)
|
||
if [ "$node_type" = "ss" ] || { [ "$node_type" = "ss-rust" ] && use_mihomo_for_ss_rust_server; }; then
|
||
local mihomo_bin="$(first_type mihomo)"
|
||
[ -x "$mihomo_bin" ] || {
|
||
echolog "Server:Mihomo 内核不存在,请确认 /usr/bin/mihomo 或 /usr/libexec/mihomo 可执行。"
|
||
return 1
|
||
}
|
||
prepare_ss_server_mihomo_runtime_config "$1" || return 1
|
||
local ss_server_workdir="$(get_ss_server_mihomo_workdir "$1")"
|
||
ln_start_bin "$mihomo_bin" ss-server-$server_count -d "$ss_server_workdir" -f "$ss_server_workdir/config.yaml"
|
||
if [ "$node_type" = "ss-rust" ]; then
|
||
echolog "Server:ss-rust 主程序不存在,已回退到 Mihomo Shadowsocks Server$server_count 启动。"
|
||
else
|
||
echolog "Server:Mihomo Shadowsocks Server$server_count Started!"
|
||
fi
|
||
else
|
||
gen_service_file ${type} $1 $TMP_PATH/ssr-server$server_count.json
|
||
if [ "$node_type" = "ssr" ]; then
|
||
ss_program="$(first_type ${type}-server)"
|
||
elif [ "$node_type" = "ss-rust" ] || [ "$node_type" = "ss" ]; then
|
||
ss_program="$(first_type ${type}server)"
|
||
fi
|
||
old_ss_program=$(readlink -f "$TMP_PATH/bin/${type}-server" 2>/dev/null)
|
||
if [ "$old_ss_program" != "$ss_program" ]; then
|
||
rm -rf "$TMP_PATH/bin/${type}-server"
|
||
fi
|
||
ln_start_bin $ss_program ${type}-server -c $TMP_PATH/ssr-server$server_count.json
|
||
echolog "Server: $(get_name ${type}) Server$server_count Started!"
|
||
fi
|
||
;;
|
||
socks5)
|
||
local listenip='-i 0.0.0.0'
|
||
[ -e /proc/sys/net/ipv6 ] && listenip='-i ::'
|
||
local username=$(uci_get_by_name $1 username)
|
||
local password=$(uci_get_by_name $1 password)
|
||
local auth_opts=""
|
||
if [ -n "$username" ]; then
|
||
auth_opts="-u $username"
|
||
[ -n "$password" ] && auth_opts="$auth_opts -P $password"
|
||
fi
|
||
ln_start_bin $(first_type microsocks) microsocks $listenip -p $(uci_get_by_name $1 server_port) -1 $auth_opts ssr-server$server_count
|
||
echolog "Server:Socks5 Server$server_count Started!"
|
||
;;
|
||
esac
|
||
server_port=$(uci_get_by_name $1 server_port)
|
||
if [ "$USE_TABLES" = "nftables" ]; then
|
||
# nftables / fw4
|
||
nft add rule inet fw4 SSR-SERVER-RULE tcp dport $server_port accept
|
||
nft add rule inet fw4 SSR-SERVER-RULE udp dport $server_port accept
|
||
elif [ "$USE_TABLES" = "iptables" ]; then
|
||
# iptables / fw3
|
||
iptables -t filter -A SSR-SERVER-RULE -p tcp --dport $server_port -j ACCEPT
|
||
iptables -t filter -A SSR-SERVER-RULE -p udp --dport $server_port -j ACCEPT
|
||
fi
|
||
return 0
|
||
}
|
||
gen_serv_include() {
|
||
local FWI=$(uci get firewall.shadowsocksr.path 2>/dev/null)
|
||
[ -n "$FWI" ] || return 0
|
||
if [ ! -f $FWI ]; then
|
||
echo '#!/bin/sh' >$FWI
|
||
fi
|
||
if [ "$USE_TABLES" = "nftables" ]; then
|
||
# nftables / fw4
|
||
cat <<-'EOF' >>"$FWI"
|
||
# 确保表存在
|
||
if nft list table inet fw4 >/dev/null 2>&1; then
|
||
# 如果不存在 SSR-SERVER-RULE 链,则创建
|
||
if ! nft list chain inet fw4 SSR-SERVER-RULE >/dev/null 2>&1; then
|
||
nft add chain inet fw4 SSR-SERVER-RULE 2>/dev/null
|
||
fi
|
||
# 从 input 链跳转到 SSR-SERVER-RULE(如果未添加)
|
||
if ! nft list chain inet fw4 input | grep -q 'jump SSR-SERVER-RULE'; then
|
||
nft insert rule inet fw4 input jump SSR-SERVER-RULE comment \"SSR Server Input Hook\" 2>/dev/null
|
||
fi
|
||
# 已存在则清空链
|
||
nft flush chain inet fw4 SSR-SERVER-RULE 2>/dev/null
|
||
fi
|
||
EOF
|
||
elif [ "$USE_TABLES" = "iptables" ]; then
|
||
# iptables / fw3
|
||
extract_rules() {
|
||
echo "*filter"
|
||
iptables-save -t filter | grep SSR-SERVER-RULE | sed -e "s/^-A INPUT/-I INPUT/"
|
||
echo 'COMMIT'
|
||
}
|
||
cat <<-EOF >>$FWI
|
||
iptables-save -c | grep -v "SSR-SERVER" | iptables-restore -c
|
||
iptables-restore -n <<-EOT
|
||
$(extract_rules)
|
||
EOT
|
||
EOF
|
||
fi
|
||
}
|
||
|
||
config_load $NAME
|
||
config_foreach server_service server_config
|
||
gen_serv_include
|
||
return 0
|
||
}
|
||
|
||
start_switch() {
|
||
if [ "$(uci_get_by_name $GLOBAL_SERVER type)" = "clash" ] || [ "$(uci_get_by_name $GLOBAL_SERVER type)" = "tuic" ]; then
|
||
echolog "提示:Mihomo 托管节点不启用 SSR Plus 自动切换逻辑。"
|
||
return 0
|
||
fi
|
||
if [ "$(uci_get_by_type global enable_switch 0)" == "1" ]; then
|
||
if [ -z "$switch_server" ]; then
|
||
local switch_time=$(uci_get_by_type global switch_time)s
|
||
local switch_timeout=$(uci_get_by_type global switch_timeout)
|
||
service_start /usr/bin/ssr-switch start $switch_time $switch_timeout
|
||
fi
|
||
fi
|
||
}
|
||
|
||
start_monitor() {
|
||
if [ $(uci_get_by_type global monitor_enable 1) == "1" ]; then
|
||
let total_count=server_count+redir_tcp+redir_udp+kcp_enable_flag+local_enable+pdnsd_enable_flag
|
||
if [ $total_count -gt 0 ]; then
|
||
service_start /usr/bin/ssr-monitor $server_count $redir_tcp $redir_udp $kcp_enable_flag $local_enable $pdnsd_enable_flag
|
||
fi
|
||
fi
|
||
}
|
||
|
||
start_xhttp_addr() {
|
||
local xhttp_addr_file="/etc/ssrplus/xhttp_address.txt"
|
||
local tmp_file="/tmp/.xhttp_addr.tmp"
|
||
|
||
# 收集所有节点的 download_address 值,去掉空行并去重排序
|
||
{
|
||
for sec in "$GLOBAL_SERVER" "$UDP_RELAY_SERVER"; do
|
||
local addr
|
||
addr=$(uci_get_by_name "$sec" download_address)
|
||
[ -n "$addr" ] && echo "$addr"
|
||
done
|
||
} | grep -v '^$' | sort -u > "$tmp_file"
|
||
|
||
# 如果没有 download_address 地址,删除旧文件并退出
|
||
if [ ! -s "$tmp_file" ]; then
|
||
[ -f "$xhttp_addr_file" ] && rm -f "$xhttp_addr_file"
|
||
rm -f "$tmp_file"
|
||
return 0
|
||
fi
|
||
|
||
# 比较 MD5 判断 download_address 地址是否有变化
|
||
local md5_new md5_old
|
||
md5_new=$(md5sum "$tmp_file" | awk '{print $1}')
|
||
if [ -f "$xhttp_addr_file" ]; then
|
||
md5_old=$(md5sum "$xhttp_addr_file" | awk '{print $1}')
|
||
else
|
||
md5_old=""
|
||
fi
|
||
|
||
# MD5 不同更新 download_address 地址文件
|
||
if [ "$md5_new" != "$md5_old" ]; then
|
||
mv -f "$tmp_file" "$xhttp_addr_file"
|
||
logger -t ssrplus-xhttp "download_address.txt updated"
|
||
else
|
||
rm -f "$tmp_file"
|
||
fi
|
||
}
|
||
|
||
start_rules() {
|
||
local server=$(get_host_ip $GLOBAL_SERVER)
|
||
local local_port=$(get_default_node_local_port)
|
||
local lan_ac_ips=$(uci_get_by_type access_control lan_ac_ips)
|
||
local lan_ac_mode=$(uci_get_by_type access_control lan_ac_mode)
|
||
if [ "$kcp_enable_flag" == "0" -a "$redir_udp" == "1" ]; then
|
||
local udp_server=$(get_host_ip $UDP_RELAY_SERVER)
|
||
local udp_local_port=$tmp_udp_port
|
||
fi
|
||
if [ -n "$lan_ac_ips" ]; then
|
||
case "$lan_ac_mode" in
|
||
w | W | b | B) local ac_ips="$lan_ac_mode$lan_ac_ips" ;;
|
||
esac
|
||
fi
|
||
gfwmode() {
|
||
case "$(normalize_run_mode)" in
|
||
gfw) echo "-g" ;;
|
||
router) echo "-r" ;;
|
||
all) echo "-z" ;;
|
||
esac
|
||
}
|
||
if [ "$(uci_get_by_type global dports)" == "3" ]; then
|
||
local custom_ports=$(uci_get_by_name $GLOBAL_SERVER custom_ports) # custom_ports 存储了用户自定义的端口
|
||
if [ -n "$custom_ports" ]; then
|
||
local proxyport="-m multiport --dports $custom_ports"
|
||
fi
|
||
else
|
||
if [ "$(uci_get_by_type global dports 1)" == "2" ]; then
|
||
local proxyport="-m multiport --dports 22,53,587,465,995,993,143,80,443,853,9418"
|
||
fi
|
||
fi
|
||
|
||
get_arg_out() {
|
||
case "$(uci_get_by_type access_control router_proxy 1)" in
|
||
1) echo "-o" ;;
|
||
2) echo "-O" ;;
|
||
esac
|
||
}
|
||
if [ "$USE_TABLES" = "nftables" ]; then
|
||
ARG_A="-A"
|
||
# Restore nft persistence rules
|
||
if [ -f "$BACKUP_FILE" ]; then
|
||
mkdir -p "$PERSIST_DIR"
|
||
mv "$BACKUP_FILE" "$PERSIST_FILE"
|
||
rm -rf "$BACKUP_DIR"
|
||
fi
|
||
elif [ "$USE_TABLES" = "iptables" ]; then
|
||
ARG_A=""
|
||
fi
|
||
/usr/share/shadowsocksr/gfw2ipset.sh
|
||
/usr/bin/ssr-rules \
|
||
-s "$server" \
|
||
-l "$local_port" \
|
||
-S "$udp_server" \
|
||
-L "$udp_local_port" \
|
||
-a "$ac_ips" \
|
||
-i "/etc/ssrplus/china_ssr.txt" \
|
||
-b "$(uci_get_by_type access_control wan_bp_ips)" \
|
||
-w "$(uci_get_by_type access_control wan_fw_ips)" \
|
||
-B "$(uci_get_by_type access_control lan_bp_ips)" \
|
||
-p "$(uci_get_by_type access_control lan_fp_ips)" \
|
||
-G "$(uci_get_by_type access_control lan_gm_ips)" \
|
||
-m "$(uci_get_by_type access_control Interface)" \
|
||
-D "$proxyport" \
|
||
$(get_arg_out) $(gfwmode) $ARG_UDP $ARG_UDP_RULES $ARG_A
|
||
|
||
return $?
|
||
}
|
||
|
||
start() {
|
||
set_lock
|
||
echolog "----------start------------"
|
||
mkdir -p /var/run /var/lock /var/log $DNSMASQ_CONF_DIR $TMP_BIN_PATH $TMP_DNSMASQ_PATH $CLASH_CONFIG_DIR
|
||
echo "conf-dir=${TMP_DNSMASQ_PATH}" >"$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf"
|
||
check_run_environment
|
||
normalize_run_mode >/dev/null
|
||
normalize_xray_protocol_nodes
|
||
if load_config; then
|
||
Start_Run
|
||
start_http_proxy
|
||
start_xhttp_addr
|
||
start_rules
|
||
start_dns
|
||
# Restore ipsets after rules creation
|
||
if [ "$HAS_IPSET" -eq 1 ]; then
|
||
for setname in gfwlist china blacklist whitelist; do
|
||
[ "$setname" = "gfwlist" ] && [ "$run_mode" != "gfw" ] && continue
|
||
if [ -f "/tmp/ssrplus_save/${setname}.save" ]; then
|
||
ipset restore -! < "/tmp/ssrplus_save/${setname}.save" 2>/dev/null
|
||
fi
|
||
done
|
||
fi
|
||
add_cron
|
||
start_switch
|
||
else
|
||
echolog "未启动主节点,禁止连接的域名正在加载。"
|
||
cat /etc/ssrplus/deny.list | sed '/^$/d' | sed '/#/d' | sed "/.*/s/.*/address=\/&\//" >$TMP_DNSMASQ_PATH/denylist.conf
|
||
echolog "禁止连接的域名加载完毕。"
|
||
if [ "$(uci_get_by_type global adblock 0)" == "1" ]; then
|
||
echolog "未启动主节点,广告过滤正在加载。"
|
||
cp -f /etc/ssrplus/ad.conf $TMP_DNSMASQ_PATH/
|
||
if [ -f "$TMP_DNSMASQ_PATH/ad.conf" ]; then
|
||
# Optimize: Batch filter using grep instead of looping sed
|
||
for list_file in /etc/ssrplus/black.list /etc/ssrplus/white.list /etc/ssrplus/deny.list; do
|
||
if [ -s "$list_file" ]; then
|
||
# Clean list file (remove comments and empty lines)
|
||
grep -vE '^\s*#|^\s*$' "$list_file" > "${list_file}.clean"
|
||
if [ -s "${list_file}.clean" ]; then
|
||
grep -v -F -f "${list_file}.clean" "$TMP_DNSMASQ_PATH/ad.conf" > "$TMP_DNSMASQ_PATH/ad.conf.tmp"
|
||
mv "$TMP_DNSMASQ_PATH/ad.conf.tmp" "$TMP_DNSMASQ_PATH/ad.conf"
|
||
fi
|
||
rm -f "${list_file}.clean"
|
||
fi
|
||
done
|
||
fi
|
||
echolog "广告过滤加载完毕。"
|
||
fi
|
||
start_http_proxy
|
||
fi
|
||
/etc/init.d/dnsmasq restart >/dev/null 2>&1
|
||
check_server
|
||
if [ "$USE_TABLES" = "nftables" ]; then
|
||
local CURRENT_SERVER="$(uci_get_by_type global global_server nil)"
|
||
if [ "$CURRENT_SERVER" != "nil" ]; then
|
||
uci set shadowsocksr.@global[0].old_global_server="$CURRENT_SERVER"
|
||
uci commit shadowsocksr
|
||
fi
|
||
fi
|
||
start_server
|
||
start_monitor
|
||
clean_log
|
||
echolog "-----------end------------"
|
||
unset_lock
|
||
}
|
||
|
||
boot() {
|
||
echolog "boot!"
|
||
mkdir -p /var/run /var/lock /var/log $DNSMASQ_CONF_DIR $TMP_BIN_PATH $TMP_DNSMASQ_PATH
|
||
start
|
||
}
|
||
|
||
stop() {
|
||
unlock
|
||
set_lock
|
||
check_run_environment
|
||
# Save ipsets before stopping to persist transparent proxy state
|
||
if [ "$HAS_IPSET" -eq 1 ]; then
|
||
mkdir -p /tmp/ssrplus_save
|
||
local run_mode="$(uci_get_by_type global run_mode)"
|
||
if [ "$run_mode" = "gfw" ]; then
|
||
ipset save gfwlist > /tmp/ssrplus_save/gfwlist.save 2>/dev/null
|
||
fi
|
||
for setname in china blacklist whitelist; do
|
||
ipset save $setname > /tmp/ssrplus_save/$setname.save 2>/dev/null
|
||
done
|
||
fi
|
||
if [ "$USE_TABLES" = "nftables" ]; then
|
||
# Save nft rules before stopping to persist transparent proxy state
|
||
if [ -f "$PERSIST_FILE" ]; then
|
||
mkdir -p "$BACKUP_DIR"
|
||
mv "$PERSIST_FILE" "$BACKUP_FILE"
|
||
fi
|
||
/usr/bin/ssr-rules -K
|
||
local OLD_SERVER="$(uci_get_by_type global old_global_server nil)"
|
||
local NEW_SERVER="$(uci_get_by_type global global_server nil)"
|
||
if [ "$OLD_SERVER" != "nil" ] && [ "$NEW_SERVER" != "nil" ] && [ "$OLD_SERVER" != "$NEW_SERVER" ]; then
|
||
/usr/bin/ssr-rules -X
|
||
fi
|
||
uci delete shadowsocksr.@global[0].old_global_server 2>/dev/null
|
||
uci commit shadowsocksr
|
||
fi
|
||
/usr/bin/ssr-rules -f
|
||
local srulecount=0
|
||
if [ "$USE_TABLES" = "nftables" ]; then
|
||
# nftables / fw4
|
||
#local srulecount=$(nft list ruleset 2>/dev/null | grep -c 'SSR-SERVER-RULE')
|
||
if nft list chain inet fw4 SSR-SERVER-RULE >/dev/null 2>&1; then
|
||
srulecount=$(nft list chain inet fw4 SSR-SERVER-RULE | grep SSR-SERVER-RULE | wc -l)
|
||
fi
|
||
elif [ "$USE_TABLES" = "iptables" ]; then
|
||
# iptables / fw3
|
||
srulecount=$(iptables -L | grep SSR-SERVER-RULE | wc -l)
|
||
fi
|
||
if [ $srulecount -gt 0 ]; then
|
||
if [ "$USE_TABLES" = "nftables" ]; then
|
||
# nftables / fw4
|
||
if nft list table inet fw4 >/dev/null 2>&1; then
|
||
if nft list chain inet fw4 SSR-SERVER-RULE >/dev/null 2>&1; then
|
||
for handle in $(nft --handle list chain inet fw4 input 2>/dev/null | \
|
||
grep 'jump SSR-SERVER-RULE' | awk '{for(i=1;i<=NF;i++) if($i=="handle") print $(i+1)}'); do
|
||
nft delete rule inet fw4 input handle $handle 2>/dev/null || true
|
||
done
|
||
nft flush chain inet fw4 SSR-SERVER-RULE 2>/dev/null || true
|
||
nft delete chain inet fw4 SSR-SERVER-RULE 2>/dev/null || true
|
||
fi
|
||
fi
|
||
elif [ "$USE_TABLES" = "iptables" ]; then
|
||
# iptables / fw3
|
||
if iptables-save -t filter | grep -q "SSR-SERVER-RULE"; then
|
||
logger -t ssr-rules "Flushing and deleting SSR-SERVER-RULE chain (iptables)"
|
||
iptables -F SSR-SERVER-RULE 2>/dev/null || true
|
||
iptables -t filter -D INPUT -j SSR-SERVER-RULE 2>/dev/null || true
|
||
iptables -X SSR-SERVER-RULE 2>/dev/null || true
|
||
fi
|
||
fi
|
||
fi
|
||
if [ -z "$switch_server" ]; then
|
||
ps_list | grep -v "grep" | grep ssr-switch | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
|
||
rm -f /var/lock/ssr-switch.lock
|
||
killall -q -9 kcptun-client
|
||
fi
|
||
ps_list | grep -v "grep" | grep ssr-monitor | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
|
||
ps_list | grep -v "grep" | grep ssr-rules | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
|
||
ps_list | grep -v "grep" | grep "sleep 0000" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
|
||
( \
|
||
# Graceful kill first, so programs have the chance to stop its subprocesses
|
||
ps_list | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill >/dev/null 2>&1 ; \
|
||
sleep 3s; \
|
||
# Force kill hanged programs
|
||
ps_list | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 ; \
|
||
)
|
||
killall -q -9 v2ray-plugin obfs-local xray-plugin shadow-tls
|
||
rm -f /var/lock/ssr-monitor.lock
|
||
if [ "$(uci -q get "dhcp.@dnsmasq[0]._unused_ssrp_changed")" = "1" ]; then
|
||
uci -q del "dhcp.@dnsmasq[0].noresolv"
|
||
uci -q del_list "dhcp.@dnsmasq[0].server"="127.0.0.1#$china_dns_port"
|
||
uci -q rename "dhcp.@dnsmasq[0]._orig_noresolv"="noresolv"
|
||
uci -q rename "dhcp.@dnsmasq[0]._orig_server"="server"
|
||
uci -q del "dhcp.@dnsmasq[0]._unused_ssrp_changed"
|
||
uci -q commit "dhcp"
|
||
fi
|
||
if [ -f "$DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf" ]; then
|
||
rm -rf $DNSMASQ_CONF_DIR/dnsmasq-ssrplus.conf \
|
||
$TMP_DNSMASQ_PATH \
|
||
$TMP_PATH/*-ssr-*.json \
|
||
$TMP_PATH/3proxy* \
|
||
$TMP_PATH/clash-* \
|
||
$TMP_PATH/ssr-server*.json \
|
||
$TMP_PATH/*-config-*.json
|
||
|
||
/etc/init.d/dnsmasq restart >/dev/null 2>&1
|
||
fi
|
||
del_cron
|
||
unset_lock
|
||
}
|
||
|
||
reset() {
|
||
stop
|
||
set_lock
|
||
rm -rf /etc/config/shadowsocksr $LOG_FILE
|
||
[ -f "/etc/ssrplus/xhttp_address.txt" ] && rm -f /etc/ssrplus/xhttp_address.txt
|
||
touch /etc/config/shadowsocksr $LOG_FILE
|
||
cp /usr/share/shadowsocksr/shadowsocksr.config /etc/config/shadowsocksr
|
||
unset_lock
|
||
}
|