diff --git a/luci-app-ssr-plus/Makefile b/luci-app-ssr-plus/Makefile index 076eb0c444c..f85525f14dd 100644 --- a/luci-app-ssr-plus/Makefile +++ b/luci-app-ssr-plus/Makefile @@ -213,9 +213,6 @@ define Package/$(PKG_NAME)/install chmod 0755 "$(1)/usr/bin/ssr-rules" 2>/dev/null || true; \ chmod 0755 "$(1)/usr/bin/ssr-switch" 2>/dev/null || true; \ fi - if [ -f "$(1)/etc/hotplug.d/iface/99-ssrplus-pppoe" ]; then \ - chmod 0755 "$(1)/etc/hotplug.d/iface/99-ssrplus-pppoe" 2>/dev/null || true; \ - fi endef # call BuildPackage - OpenWrt buildroot signature diff --git a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua index d43a1e8d706..ddb10ca5d28 100644 --- a/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua +++ b/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua @@ -168,6 +168,39 @@ o.write = function() luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers")) end +-- [[ Startup Internet Check ]]-- +s = m:section(TypedSection, "startup_check", translate("Startup Connectivity Check"), translate("On boot, wait until the internet is reachable before starting the service, so that node domains can be resolved when the WAN link (e.g. PPPoE) comes up late. Manual restarts are never delayed. Disable this if SSR Plus is only used as a LAN-only proxy.")) +s.anonymous = true + +o = s:option(Flag, "enabled", translate("Enable")) +o.default = "1" +o.rmempty = false + +o = s:option(ListValue, "method", translate("Check Method"), translate("DNS resolution matches what the service actually needs at startup; ICMP ping is faster but does not prove that DNS is ready.")) +o:value("dns", translate("DNS resolution")) +o:value("ping", translate("ICMP ping")) +o.default = "dns" +o:depends("enabled", "1") + +o = s:option(DynamicList, "host", translate("Check Servers"), translate("DNS servers to query, or hosts to ping. The check passes as soon as any one of them responds.")) +o.datatype = "ipaddr" +o:depends("enabled", "1") + +o = s:option(Value, "domain", translate("Resolve Domain"), translate("Domain name used for the DNS resolution test.")) +o.datatype = "hostname" +o.default = "www.baidu.com" +o:depends({enabled = "1", method = "dns"}) + +o = s:option(Value, "retry", translate("Max Retries")) +o.datatype = "uinteger" +o.default = 30 +o:depends("enabled", "1") + +o = s:option(Value, "interval", translate("Retry Interval(second)"), translate("Maximum wait is Max Retries multiplied by Retry Interval.")) +o.datatype = "uinteger" +o.default = 2 +o:depends("enabled", "1") + -- [[ SOCKS5 Proxy ]]-- s = m:section(TypedSection, "socks5_proxy", translate("Global SOCKS5 Proxy Server")) s.anonymous = true diff --git a/luci-app-ssr-plus/po/templates/ssr-plus.pot b/luci-app-ssr-plus/po/templates/ssr-plus.pot index 830e57e64a3..b0a18a152d1 100644 --- a/luci-app-ssr-plus/po/templates/ssr-plus.pot +++ b/luci-app-ssr-plus/po/templates/ssr-plus.pot @@ -3362,3 +3362,63 @@ msgstr "" #: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/client-config.lua:1667 msgid "xudpProxyUDP443" msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:171 +msgid "Startup Connectivity Check" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:171 +msgid "" +"On boot, wait until the internet is reachable before starting the service, " +"so that node domains can be resolved when the WAN link (e.g. PPPoE) comes up " +"late. Manual restarts are never delayed. Disable this if SSR Plus is only " +"used as a LAN-only proxy." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:178 +msgid "Check Method" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:178 +msgid "" +"DNS resolution matches what the service actually needs at startup; ICMP ping " +"is faster but does not prove that DNS is ready." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:179 +msgid "DNS resolution" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:180 +msgid "ICMP ping" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:184 +msgid "Check Servers" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:184 +msgid "" +"DNS servers to query, or hosts to ping. The check passes as soon as any one " +"of them responds." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:188 +msgid "Resolve Domain" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:188 +msgid "Domain name used for the DNS resolution test." +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:193 +msgid "Max Retries" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:198 +msgid "Retry Interval(second)" +msgstr "" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:198 +msgid "Maximum wait is Max Retries multiplied by Retry Interval." +msgstr "" diff --git a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po index 5382a3f32a9..7ed901becb9 100644 --- a/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po +++ b/luci-app-ssr-plus/po/zh_Hans/ssr-plus.po @@ -3394,6 +3394,69 @@ msgstr "UDP 最大并发连接数" msgid "xudpProxyUDP443" msgstr "对被代理的 UDP/443 流量处理方式" +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:171 +msgid "Startup Connectivity Check" +msgstr "启动前连通性检查" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:171 +msgid "" +"On boot, wait until the internet is reachable before starting the service, " +"so that node domains can be resolved when the WAN link (e.g. PPPoE) comes up " +"late. Manual restarts are never delayed. Disable this if SSR Plus is only " +"used as a LAN-only proxy." +msgstr "" +"开机时等待互联网可达后再启动服务,避免 WAN 链路(如 PPPoE)拨号较慢时节点域名" +"解析失败。手动重启不会被延迟。若 SSR Plus 仅用作纯内网代理,可关闭此项。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:178 +msgid "Check Method" +msgstr "检查方式" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:178 +msgid "" +"DNS resolution matches what the service actually needs at startup; ICMP ping " +"is faster but does not prove that DNS is ready." +msgstr "" +"DNS 解析对应服务启动时真正依赖的条件;ICMP ping 更快,但无法证明 DNS 已就绪。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:179 +msgid "DNS resolution" +msgstr "DNS 解析" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:180 +msgid "ICMP ping" +msgstr "ICMP Ping" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:184 +msgid "Check Servers" +msgstr "检查服务器" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:184 +msgid "" +"DNS servers to query, or hosts to ping. The check passes as soon as any one " +"of them responds." +msgstr "用于查询的 DNS 服务器或 Ping 目标地址。任意一个响应即视为通过。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:188 +msgid "Resolve Domain" +msgstr "解析测试域名" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:188 +msgid "Domain name used for the DNS resolution test." +msgstr "用于 DNS 解析测试的域名。" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:193 +msgid "Max Retries" +msgstr "最大重试次数" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:198 +msgid "Retry Interval(second)" +msgstr "重试间隔(秒)" + +#: applications/luci-app-ssr-plus/luasrc/model/cbi/shadowsocksr/advanced.lua:198 +msgid "Maximum wait is Max Retries multiplied by Retry Interval." +msgstr "最长等待时间为最大重试次数乘以重试间隔。" + #~ msgid "CHECK AND UPDATE" #~ msgstr "检查并更新" diff --git a/luci-app-ssr-plus/root/etc/hotplug.d/iface/99-ssrplus-pppoe b/luci-app-ssr-plus/root/etc/hotplug.d/iface/99-ssrplus-pppoe deleted file mode 100644 index fc969f71110..00000000000 --- a/luci-app-ssr-plus/root/etc/hotplug.d/iface/99-ssrplus-pppoe +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -[ "$ACTION" = "ifup" ] || exit 0 -[ "$INTERFACE" = "wan" ] || exit 0 - -WAN_PROTO="$(uci -q get network.wan.proto 2>/dev/null)" -[ "$WAN_PROTO" = "pppoe" ] || exit 0 - -LOCK_FILE="/var/run/ssrplus-pppoe-hotplug.lock" - -( - if ! mkdir "$LOCK_FILE" 2>/dev/null; then - exit 0 - fi - - trap 'rmdir "$LOCK_FILE" 2>/dev/null' EXIT INT TERM - - sleep 10 - /etc/init.d/shadowsocksr restart >/dev/null 2>&1 -) >/dev/null 2>&1 & - -exit 0 diff --git a/luci-app-ssr-plus/root/etc/init.d/shadowsocksr b/luci-app-ssr-plus/root/etc/init.d/shadowsocksr index 41df5c1c267..749d1f723dc 100755 --- a/luci-app-ssr-plus/root/etc/init.d/shadowsocksr +++ b/luci-app-ssr-plus/root/etc/init.d/shadowsocksr @@ -2920,6 +2920,80 @@ start_rules() { return $? } +startup_check_probe() { + local method="$1" + local host="$2" + local domain="$3" + + case "$method" in + ping) + if echo "$host" | grep -q ':'; then + ping6 -c1 -W2 "$host" >/dev/null 2>&1 + else + ping -c1 -W2 "$host" >/dev/null 2>&1 + fi + ;; + *) + # Prefer nslookup, which queries the configured server directly and so + # does not depend on /etc/resolv.conf being populated yet. busybox does + # not always ship the nslookup applet, so fall back to resolveip, which + # ships with netifd and is always present, resolving via the system + # resolver - the same path get_host_ip() relies on at startup. + if command -v nslookup >/dev/null 2>&1; then + if command -v timeout >/dev/null 2>&1; then + timeout 3 nslookup "$domain" "$host" >/dev/null 2>&1 + else + nslookup "$domain" "$host" >/dev/null 2>&1 + fi + else + resolveip -4 -t 3 "$domain" >/dev/null 2>&1 + fi + ;; + esac +} + +wait_for_internet() { + local method hosts domain max_retries interval retry host + + # Only gate the boot path: a manual restart must not block the LuCI UI. + [ "${SSR_BOOT:-0}" = "1" ] || return 0 + [ "$(uci_get_by_type startup_check enabled 1)" = "1" ] || return 0 + # 未配置主节点相当于未启用服务,没有需要解析的域名,无需等待 + [ "$(uci_get_by_type global global_server nil)" != "nil" ] || return 0 + + hosts="$(uci_get_by_type startup_check host "223.5.5.5 119.29.29.29")" + # Unquoted on purpose: word splitting collapses a whitespace-only list to "" + hosts="$(echo $hosts)" + [ -n "$hosts" ] || return 0 + + method="$(uci_get_by_type startup_check method dns)" + domain="$(uci_get_by_type startup_check domain www.baidu.com)" + max_retries="$(uci_get_by_type startup_check retry 30)" + interval="$(uci_get_by_type startup_check interval 2)" + + # Guard against blank/invalid values left behind by the UI + echo "$max_retries" | grep -Eq '^[0-9]+$' || max_retries=30 + echo "$interval" | grep -Eq '^[0-9]+$' || interval=2 + [ "$interval" -lt 1 ] && interval=1 + [ "$method" = "ping" ] || [ -n "$domain" ] || domain="www.baidu.com" + + retry=0 + while [ "$retry" -lt "$max_retries" ]; do + for host in $hosts; do + if startup_check_probe "$method" "$host" "$domain"; then + echolog "Internet is reachable via $host ($method, attempt $((retry + 1)))" + return 0 + fi + done + + retry=$((retry + 1)) + echolog "Waiting for internet access... ($retry/$max_retries)" + sleep "$interval" + done + + echolog "Warning: Internet access timeout (${max_retries} retries), starting anyway" +} + start() { local run_mode local CURRENT_SERVER @@ -2932,6 +3006,7 @@ 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 + wait_for_internet normalize_run_mode >/dev/null normalize_xray_protocol_nodes @@ -3023,6 +3098,7 @@ start() { boot() { echolog "boot!" mkdir -p /var/run /var/lock /var/log "$DNSMASQ_CONF_DIR" "$TMP_BIN_PATH" "$TMP_DNSMASQ_PATH" + SSR_BOOT=1 start } diff --git a/luci-app-ssr-plus/root/etc/uci-defaults/luci-ssr-plus b/luci-app-ssr-plus/root/etc/uci-defaults/luci-ssr-plus index 3e69e08aeb4..d125200c0c3 100755 --- a/luci-app-ssr-plus/root/etc/uci-defaults/luci-ssr-plus +++ b/luci-app-ssr-plus/root/etc/uci-defaults/luci-ssr-plus @@ -68,6 +68,17 @@ if [ -s "/etc/config/shadowsocksr" ]; then uci -q set shadowsocksr.@global_xray_fragment[0].noise='0' fi + if ! uci -q get shadowsocksr.@startup_check[0] > /dev/null; then + uci -q add shadowsocksr startup_check + uci -q set shadowsocksr.@startup_check[0].enabled='1' + uci -q set shadowsocksr.@startup_check[0].method='dns' + uci -q add_list shadowsocksr.@startup_check[0].host='223.5.5.5' + uci -q add_list shadowsocksr.@startup_check[0].host='119.29.29.29' + uci -q set shadowsocksr.@startup_check[0].domain='www.baidu.com' + uci -q set shadowsocksr.@startup_check[0].retry='30' + uci -q set shadowsocksr.@startup_check[0].interval='2' + fi + if ! uci -q get shadowsocksr.@global[0].component_mirror > /dev/null; then uci -q set shadowsocksr.@global[0].component_mirror='direct' fi diff --git a/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config b/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config index 819ce7641e4..ab11594b86f 100644 --- a/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config +++ b/luci-app-ssr-plus/root/usr/share/shadowsocksr/shadowsocksr.config @@ -59,6 +59,15 @@ config global_xray_fragment option fragment '0' option noise '0' +config startup_check + option enabled '1' + option method 'dns' + list host '223.5.5.5' + list host '119.29.29.29' + option domain 'www.baidu.com' + option retry '30' + option interval '2' + config clash_client_group option enabled '0' option remarks ''