#!/bin/sh # # Copyright (C) 2017 openwrt-ssr # Copyright (C) 2017 yushi studio # # 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" | 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" | 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}" 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" if [ "$server_type" = "v2ray" ]; then export SSR_SWITCH_PROBE=1 gen_config_file "$LOCAL_SERVER" "$server_type" 4 0 "$probe_socks_port" >/tmp/ssr-switch-probe.log 2>&1 || return 1 unset SSR_SWITCH_PROBE local xray_bin xray_bin="$(first_type xray)" [ -x "$xray_bin" ] || return 1 "$xray_bin" run -c "$local_config_file" >>/tmp/ssr-switch-probe.log 2>&1 & else 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 || return 1 unset SSR_SWITCH_PROBE_INSTANCE_KEY unset SSR_SWITCH_PROBE fi 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