fix(proxy): harden strategy group selection

This commit is contained in:
Xiang Li
2026-04-29 22:53:43 +08:00
parent 9f30995430
commit 25bdb43a80
+97 -21
View File
@@ -266,7 +266,13 @@ proxy_group_exists() {
[ -n "${group:-}" ] || return 1
[ "$(proxy_groups_json | "$(yq_bin)" -p=json eval ".proxies | has(\"$group\")" - 2>/dev/null)" = "true" ]
[ "$(
GROUP="$group" proxy_groups_json \
| GROUP="$group" "$(yq_bin)" -p=json eval \
'.proxies | to_entries | .[] | select(.key == env(GROUP)) | "true"' \
- 2>/dev/null \
| head -n 1
)" = "true" ]
}
proxy_group_type() {
@@ -275,7 +281,10 @@ proxy_group_type() {
[ -n "${group:-}" ] || die "策略组名称不能为空"
proxy_group_exists "$group" || die "策略组不存在:$group"
proxy_groups_json | "$(yq_bin)" -p=json eval ".proxies.\"$group\".type // \"\"" - 2>/dev/null
GROUP="$group" proxy_groups_json \
| GROUP="$group" "$(yq_bin)" -p=json eval \
'.proxies | to_entries | .[] | select(.key == env(GROUP)) | .value.type // ""' \
- 2>/dev/null
}
proxy_group_type_key() {
@@ -378,8 +387,10 @@ proxy_group_current() {
[ -n "${group:-}" ] || die "策略组名称不能为空"
proxy_group_exists "$group" || die "策略组不存在:$group"
proxy_groups_json \
| "$(yq_bin)" -p=json eval ".proxies.\"$group\".now // \"\"" - 2>/dev/null
GROUP="$group" proxy_groups_json \
| GROUP="$group" "$(yq_bin)" -p=json eval \
'.proxies | to_entries | .[] | select(.key == env(GROUP)) | .value.now // ""' \
- 2>/dev/null
}
proxy_group_nodes() {
@@ -388,8 +399,10 @@ proxy_group_nodes() {
[ -n "${group:-}" ] || die "策略组名称不能为空"
proxy_group_exists "$group" || die "策略组不存在:$group"
proxy_groups_json \
| "$(yq_bin)" -p=json eval ".proxies.\"$group\".all[] // \"\"" - 2>/dev/null
GROUP="$group" proxy_groups_json \
| GROUP="$group" "$(yq_bin)" -p=json eval \
'.proxies | to_entries | .[] | select(.key == env(GROUP)) | .value.all[] // ""' \
- 2>/dev/null
}
proxy_node_is_descriptive_entry() {
@@ -479,8 +492,10 @@ proxy_group_supports_manual_pick() {
[ "$type_key" = "selector" ] || return 1
has_now="$(
proxy_groups_json \
| "$(yq_bin)" -p=json eval ".proxies.\"$group\".now != null" - 2>/dev/null \
GROUP="$group" proxy_groups_json \
| GROUP="$group" "$(yq_bin)" -p=json eval \
'.proxies | to_entries | .[] | select(.key == env(GROUP)) | .value.now != null' \
- 2>/dev/null \
| head -n 1
)"
[ "${has_now:-false}" = "true" ] || return 1
@@ -742,6 +757,37 @@ print_proxy_groups_summary() {
done < <(proxy_group_list)
}
proxy_node_test_delay() {
local node="$1"
local url="${2:-http://www.gstatic.com/generate_204}"
local timeout_ms="${3:-3000}"
local encoded_node
[ -n "${node:-}" ] || return 1
encoded_node="$(proxy_node_url_encode "$node")"
controller_curl GET "/proxies/${encoded_node}/delay?timeout=${timeout_ms}&url=${url}" 2>/dev/null \
| "$(yq_bin)" -p=json eval '.delay // 0' - 2>/dev/null \
| head -n 1
}
proxy_group_nodes_delay_map() {
local group="$1"
[ -n "${group:-}" ] || return 1
GROUP="$group" proxy_groups_json 2>/dev/null \
| GROUP="$group" "$(yq_bin)" -p=json eval '
. as $root |
(.proxies | to_entries | .[] | select(.key == env(GROUP)) | .value.all // []) | .[] |
. as $n |
$n + "\t" + (
($root.proxies[$n].history | select(length > 0) | .[-1].delay // 0) // 0 |
tostring
)
' - 2>/dev/null
}
proxy_node_match_key() {
local node="$1"
@@ -833,13 +879,40 @@ proxy_group_current_display() {
}
proxy_group_display_list() {
local group
proxy_groups_json \
| "$(yq_bin)" -p=json eval '
.proxies | to_entries | .[] |
select(.value.all != null) |
select(
(.value.type // "" | downcase | sub("[-_ ]+"; "")) as $t |
($t == "selector" or $t == "urltest" or $t == "fallback" or $t == "loadbalance")
) |
.key
' - 2>/dev/null
}
while IFS= read -r group; do
[ -n "${group:-}" ] || continue
proxy_group_can_show_candidates "$group" || continue
echo "$group"
done < <(proxy_group_list)
proxy_node_url_encode() {
local byte code char out=""
while IFS= read -r byte; do
[ -n "${byte:-}" ] || continue
code=$((16#$byte))
if { [ "$code" -ge 48 ] && [ "$code" -le 57 ]; } \
|| { [ "$code" -ge 65 ] && [ "$code" -le 90 ]; } \
|| { [ "$code" -ge 97 ] && [ "$code" -le 122 ]; } \
|| [ "$byte" = "2d" ] || [ "$byte" = "2e" ] || [ "$byte" = "5f" ] || [ "$byte" = "7e" ]; then
printf -v char "\\x$byte"
out+="$char"
else
out+="%${byte^^}"
fi
done < <(printf '%s' "$1" | od -An -tx1 -v | tr ' ' '\n')
printf '%s' "$out"
}
proxy_json_string_escape() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}
proxy_group_select() {
@@ -848,11 +921,12 @@ proxy_group_select() {
local base secret
local code response_file response_body
local available_node found
local group_enc node_json
[ -n "${group:-}" ] || die "绛栫暐缁勫悕绉颁笉鑳戒负绌?"
[ -n "${node:-}" ] || die "鑺傜偣鍚嶇О涓嶈兘涓虹┖"
[ -n "${group:-}" ] || die "策略组名称不能为空"
[ -n "${node:-}" ] || die "节点名称不能为空"
proxy_group_exists "$group" || die "绛栫暐缁勪笉瀛樺湪锛?group"
proxy_group_exists "$group" || die "策略组不存在:$group"
proxy_group_can_show_candidates "$group" || die "$(proxy_group_manual_pick_error_message "$group")"
proxy_node_is_selectable_candidate "$node" || die "节点不是可切换节点:$node"
@@ -866,18 +940,20 @@ proxy_group_select() {
done < <(proxy_group_selectable_nodes "$group")
if [ "$found" != "true" ]; then
die "鑺傜偣涓嶅瓨鍦ㄤ簬绛栫暐缁勪腑锛?group -> $node"
die "节点不存在于策略组中:$group -> $node"
fi
base="$(controller_api_base)"
secret="$(controller_secret)"
group_enc="$(proxy_node_url_encode "$group")"
node_json="$(proxy_json_string_escape "$node")"
response_file="$(mktemp)"
code="$(
curl -sS -o "$response_file" -w "%{http_code}" -X PUT \
-H "Content-Type: application/json" \
${secret:+-H "Authorization: Bearer $secret"} \
--data "{\"name\":\"$node\"}" \
"$base/proxies/$group"
--data "{\"name\":\"$node_json\"}" \
"$base/proxies/$group_enc"
)"
if [ "${code:-000}" -lt 200 ] || [ "${code:-000}" -ge 300 ]; then
@@ -886,7 +962,7 @@ proxy_group_select() {
if [ -n "${response_body:-}" ]; then
die "controller 原始错误:$response_body"
fi
die "鑺傜偣鍒囨崲澶辫触锛歝ontroller 杩斿洖 HTTP $code"
die "节点切换失败:controller 返回 HTTP $code"
fi
rm -f "$response_file" 2>/dev/null || true