small-packages/luci-app-ssr-plus/root/usr/bin/ssr-switch
2026-06-21 16:24:44 +08:00

275 lines
6.5 KiB
Bash
Executable File

#!/bin/sh
#
# Copyright (C) 2017 openwrt-ssr
# Copyright (C) 2017 yushi studio <ywb94@qq.com>
#
# This is free software, licensed under the GNU General Public License v3.
# See /LICENSE for more information.
#
ORIG_ARG1="${1:-}"
ORIG_ARG2="${2:-}"
ORIG_ARG3="${3:-}"
[ -f /lib/functions.sh ] && . /lib/functions.sh
. $IPKG_INSTROOT/etc/init.d/shadowsocksr
LOCK_FILE="/var/lock/ssr-switch.lock"
PROBE_SID=""
cycle_time=60
switch_time=3
probe_socks_port=10800
direct_fail_count=0
direct_fail_limit=3
probe_url="$(uci_get_by_type server_subscribe url_test_url)"
[ -n "$probe_url" ] || probe_url="https://www.google.com/generate_204"
PROBE_INSTANCE_KEY="ssr-switch-probe-local"
DEFAULT_SERVER="$(uci_get_by_type global global_server)"
CURRENT_SERVER="$DEFAULT_SERVER"
ENABLE_SERVER="nil"
cleanup_probe_socks() {
ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml|v2ray-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill >/dev/null 2>&1
sleep 1
ps_list | grep -v "grep" | grep -E "tcp-http-ssr-switch-local\\.json|ss-${PROBE_INSTANCE_KEY}/config\\.yaml|tuic-${PROBE_INSTANCE_KEY}/config\\.yaml|v2ray-${PROBE_INSTANCE_KEY}/config\\.yaml" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1
rm -f "$TMP_PATH/tcp-http-ssr-switch-local.json" 2>/dev/null
rm -rf "$TMP_PATH/ss-${PROBE_INSTANCE_KEY}" "$TMP_PATH/tuic-${PROBE_INSTANCE_KEY}" "$TMP_PATH/v2ray-${PROBE_INSTANCE_KEY}" 2>/dev/null
rm -f /tmp/ssr-switch-probe.log 2>/dev/null
}
start_probe_socks() {
local sid="$1"
local server_type
server_type="$(uci_get_by_name "$sid" type)"
[ "$server_type" = "clash" ] && return 1
[ "$server_type" = "tuic" ] && return 1
[ "$server_type" = "socks5" ] && return 1
cleanup_probe_socks
LOCAL_SERVER="$sid"
local_config_file="$TMP_PATH/tcp-http-ssr-switch-local.json"
_local="2"
local_enable=0
tmp_local_port=
mode="tcp,udp"
export SSR_SWITCH_PROBE=1
export SSR_SWITCH_PROBE_INSTANCE_KEY="$PROBE_INSTANCE_KEY"
start_local_with_port "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1
local rc=$?
unset SSR_SWITCH_PROBE_INSTANCE_KEY
unset SSR_SWITCH_PROBE
[ "$rc" = "0" ] || return 1
sleep 2
return 0
}
probe_http_ok() {
case "$1" in
[1-5][0-9][0-9])
return 0
;;
esac
return 1
}
check_direct_probe() {
local code
code="$(curl -k -sS -L -o /dev/null \
--connect-timeout "$switch_time" \
--max-time "$switch_time" \
-w '%{http_code}' \
"$probe_url" 2>/dev/null || true)"
probe_http_ok "$code"
}
check_proxy_via_socks() {
local try_count
local code
local proxy_host
try_count="$(uci_get_by_type global switch_try_count 3)"
for i in $(seq 1 "$try_count"); do
for proxy_host in "127.0.0.1:$probe_socks_port" "[::1]:$probe_socks_port"; do
code="$(curl -k -sS -L -o /dev/null \
--connect-timeout "$switch_time" \
--max-time "$switch_time" \
--socks5-hostname "$proxy_host" \
-w '%{http_code}' \
"$probe_url" 2>/dev/null || true)"
probe_http_ok "$code" && return 0
done
sleep 1
done
return 1
}
test_proxy() {
local sid="$1"
[ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1
[ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1
start_probe_socks "$sid" || {
cleanup_probe_socks
return 1
}
check_proxy_via_socks
local ret=$?
cleanup_probe_socks
return $ret
}
search_proxy() {
local sid="$1"
[ "$(uci_get_by_name "$sid" switch_enable 0)" = "1" ] || return 1
[ "$(uci_get_by_name "$sid" type)" = "clash" ] && return 1
[ "$(uci_get_by_name "$sid" type)" = "tuic" ] && return 1
[ "$sid" = "$CURRENT_SERVER" ] && return 1
[ "$ENABLE_SERVER" != "nil" ] && return 0
if test_proxy "$sid"; then
ENABLE_SERVER="$sid"
return 0
fi
return 1
}
select_proxy() {
config_load "$NAME"
ENABLE_SERVER="nil"
config_foreach search_proxy servers
}
set_main_server() {
local sid="$1"
[ -n "$sid" ] || return 1
uci set shadowsocksr.@global[0].global_server="$sid"
uci commit shadowsocksr
return 0
}
switch_proxy() {
set_main_server "$1" || return 1
/etc/init.d/shadowsocksr restart "$1"
return 0
}
start() {
while [ "1" = "1" ]; do
sleep "0000$cycle_time"
run_once
done
}
run_once() {
DEFAULT_SERVER="$(uci_get_by_type global global_server)"
[ "$DEFAULT_SERVER" = "nil" ] && {
direct_fail_count=0
return 0
}
[ "$(uci_get_by_name "$DEFAULT_SERVER" type)" = "clash" ] && {
direct_fail_count=0
cleanup_probe_socks
return 0
}
[ "$(uci_get_by_name "$DEFAULT_SERVER" type)" = "tuic" ] && {
direct_fail_count=0
cleanup_probe_socks
return 0
}
[ "$(uci_get_by_name "$DEFAULT_SERVER" kcp_enable 0)" = "1" ] && {
direct_fail_count=0
cleanup_probe_socks
return 1
}
CURRENT_SERVER="$DEFAULT_SERVER"
if check_direct_probe; then
direct_fail_count=0
return 0
fi
direct_fail_count=$((direct_fail_count + 1))
[ "$direct_fail_count" -lt "$direct_fail_limit" ] && return 0
direct_fail_count=0
if test_proxy "$CURRENT_SERVER"; then
return 0
fi
echolog "Current server error, try to switch another server."
select_proxy
if [ "$ENABLE_SERVER" != "nil" ]; then
CURRENT_SERVER="$ENABLE_SERVER"
switch_proxy "$CURRENT_SERVER"
echolog "Switch to $(uci_get_by_name "$CURRENT_SERVER" alias) proxy!"
else
switch_proxy "$CURRENT_SERVER"
echolog "Try restart current server."
fi
return 0
}
probe_once() {
local sid="$1"
[ -n "$sid" ] || {
echo "missing_sid"
return 1
}
cleanup_probe_socks
if ! start_probe_socks "$sid"; then
echo "probe_start:fail"
return 1
fi
echo "probe_start:ok"
echo "probe:curl_ipv4"
curl -k -sS -L -o /dev/null \
--connect-timeout "$switch_time" \
--max-time "$switch_time" \
--socks5-hostname "127.0.0.1:$probe_socks_port" \
-w 'code=%{http_code}\n' \
"$probe_url" 2>/dev/null || true
echo "probe:curl_ipv6"
curl -g -k -sS -L -o /dev/null \
--connect-timeout "$switch_time" \
--max-time "$switch_time" \
--socks5-hostname "[::1]:$probe_socks_port" \
-w 'code=%{http_code}\n' \
"$probe_url" 2>/dev/null || true
cleanup_probe_socks
return 0
}
main() {
case "${ORIG_ARG1:-}" in
probe)
PROBE_SID="${ORIG_ARG2:-}"
probe_once "$PROBE_SID"
;;
once)
run_once
;;
start)
[ -n "$ORIG_ARG2" ] && cycle_time="$ORIG_ARG2"
[ -n "$ORIG_ARG3" ] && switch_time="$ORIG_ARG3"
[ -f "$LOCK_FILE" ] && exit 2
touch "$LOCK_FILE"
start
;;
*)
[ -n "$ORIG_ARG1" ] && cycle_time="$ORIG_ARG1"
[ -n "$ORIG_ARG2" ] && switch_time="$ORIG_ARG2"
[ -f "$LOCK_FILE" ] && exit 2
touch "$LOCK_FILE"
start
;;
esac
}
if [ "${0##*/}" = "ssr-switch" ]; then
main "$@"
fi