mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-05 01:44:16 +08:00
⛅ Sync 2026-10-03 02:37:08
This commit is contained in:
1 parent
1b126bfea3
commit
a5bb91e6ed
24 files changed
+5536
-990
No files matched your search
+889
@@ -0,0 +1,889 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
# Security: Define allowed operations only
|
||||
# No arbitrary command execution allowed
|
||||
|
||||
# --- Moon helpers -------------------------------------------------------
|
||||
#
|
||||
# A ZeroTier moon is identified by a 40-bit address rendered as exactly 10
|
||||
# hex digits, but `zerotier-cli listmoons` reports the world id zero-padded to
|
||||
# 16 (e.g. 000000123456abcd). The daemon's control-plane route is
|
||||
# /moon/([0-9a-fA-F]{10})
|
||||
# which matches EXACTLY 10 characters -- so passing the padded form, a
|
||||
# stripped form, or the docs' 14-char form all 404. Every id crossing this
|
||||
# boundary is therefore normalized to exactly 10 hex digits.
|
||||
#
|
||||
# `zerotier-cli orbit` is also a silent no-op (exit 0, no output, nothing
|
||||
# done) when the seed argument is zero -- the C++ is
|
||||
# `if ((worldId) && (seed)) { ... }` with no else branch. And
|
||||
# `zerotier-cli deorbit` returns "200 deorbit OK" for a moon that does not
|
||||
# exist. Exit codes are therefore never trusted: both mutating methods
|
||||
# verify the actual result by diffing `listmoons`.
|
||||
|
||||
# Strip leading zeros (keeping >=1 char), then left-pad back to 10 digits.
|
||||
# Rejects non-hex input and anything still wider than 10 digits after
|
||||
# normalization, which could never match the route anyway.
|
||||
moon_norm_id() {
|
||||
local v="$1"
|
||||
while [ "${#v}" -gt 1 ]; do
|
||||
case "$v" in
|
||||
0*) v="${v#0}" ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
while [ "${#v}" -lt 10 ]; do
|
||||
v="0$v"
|
||||
done
|
||||
case "$v" in
|
||||
*[!0-9a-fA-F]*) return 1 ;;
|
||||
esac
|
||||
[ "${#v}" -eq 10 ] || return 1
|
||||
# the zero address is never a valid moon id
|
||||
[ "$v" != "0000000000" ] || return 1
|
||||
printf '%s' "$v"
|
||||
}
|
||||
|
||||
# Persisted moon world files, one 16-digit hex name per line, from the
|
||||
# configured persistent config dir. Orbit persistence in ZeroTier 1.16 IS
|
||||
# the moons.d/<id>.moon file: there is no separate .orbit state object
|
||||
# (ZT_STATE_OBJECT_* covers only identity/planet/moon/peer/network-config),
|
||||
# and `deorbit` unlinks that file. zerotier-sync.sh already mirrors moons.d.
|
||||
moon_persisted_list() {
|
||||
local d
|
||||
d=$(uci -q get zerotier.global.config_path 2>/dev/null)
|
||||
[ -n "$d" ] || d="/etc/zerotier"
|
||||
[ -d "$d/moons.d" ] || return 0
|
||||
find "$d/moons.d" -type f -name '*.moon' 2>/dev/null | while read -r f; do
|
||||
printf '%s\n' "$(basename "$f" .moon)"
|
||||
done
|
||||
}
|
||||
|
||||
moon_is_listed() {
|
||||
# $1 = 10-digit id, $2 = listmoons output.
|
||||
# The daemon reports world ids as %.16llx, i.e. 16 hex digits zero-padded
|
||||
# on the left. Our id is always normalized to 10 digits, so padding it
|
||||
# with 6 zeros reproduces exactly the daemon's form for every id whose
|
||||
# significant length is <= 10 (16-L zeros + L digits either way).
|
||||
local id="$1" out="$2"
|
||||
case "$out" in
|
||||
*"\"000000$id\""*) return 0 ;;
|
||||
esac
|
||||
return 1
|
||||
}
|
||||
|
||||
# Wait for an orbited moon to show up in `listmoons`.
|
||||
#
|
||||
# An orbit RPC only registers the seed in RAM (Topology::_moonSeeds); the moon
|
||||
# becomes visible in `listmoons` -- and only becomes persistent as
|
||||
# moons.d/<id>.moon -- once a root peer has delivered the signed world. That
|
||||
# handshake takes a moment, so an immediate re-check would report a false
|
||||
# failure for a perfectly valid orbit against a reachable moon. Poll for a
|
||||
# bounded grace period instead; a genuinely unreachable moon never appears.
|
||||
#
|
||||
# $1 = 10-digit id, $2 = attempts, $3 = seconds between attempts.
|
||||
moon_wait_listed() {
|
||||
local id="$1" tries="$2" nap="$3" i out=""
|
||||
i=0
|
||||
while [ "$i" -lt "$tries" ]; do
|
||||
out=$(zerotier-cli listmoons 2>/dev/null)
|
||||
if moon_is_listed "$id" "$out"; then
|
||||
return 0
|
||||
fi
|
||||
i=$((i + 1))
|
||||
[ "$i" -lt "$tries" ] && sleep "$nap"
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
json_init
|
||||
json_add_object "reload"
|
||||
json_close_object
|
||||
json_add_object "status"
|
||||
json_close_object
|
||||
json_add_object "get_networks_peers"
|
||||
json_close_object
|
||||
json_add_object "list_moons"
|
||||
json_close_object
|
||||
json_add_object "orbit_moon"
|
||||
json_add_string "moon_id" "string"
|
||||
json_add_string "seed" "string"
|
||||
json_close_object
|
||||
json_add_object "deorbit_moon"
|
||||
json_add_string "moon_id" "string"
|
||||
json_close_object
|
||||
json_add_object "ping_networks"
|
||||
json_close_object
|
||||
json_add_object "sync_config"
|
||||
json_add_string "local_conf_path" "path"
|
||||
json_close_object
|
||||
json_add_object "remote_list"
|
||||
json_close_object
|
||||
json_add_object "remote_host_set"
|
||||
json_add_string "section" "string"
|
||||
json_add_string "name" "string"
|
||||
json_add_string "host" "string"
|
||||
json_add_string "port" "string"
|
||||
json_add_string "user" "string"
|
||||
json_add_string "controller_port" "string"
|
||||
json_add_string "private_key" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_host_del"
|
||||
json_add_string "section" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_diagnose"
|
||||
json_add_string "section" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_ctl_get"
|
||||
json_add_string "section" "string"
|
||||
json_add_string "path" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_member_list"
|
||||
json_add_string "section" "string"
|
||||
json_add_string "nwid" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_peer_list"
|
||||
json_add_string "section" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_network_set"
|
||||
json_add_string "section" "string"
|
||||
json_add_string "nwid" "string"
|
||||
json_add_string "body" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_network_del"
|
||||
json_add_string "section" "string"
|
||||
json_add_string "nwid" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_member_set"
|
||||
json_add_string "section" "string"
|
||||
json_add_string "nwid" "string"
|
||||
json_add_string "member_id" "string"
|
||||
json_add_string "body" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_moon_plan"
|
||||
json_add_string "section" "string"
|
||||
json_close_object
|
||||
json_add_object "remote_moon_apply"
|
||||
json_add_string "section" "string"
|
||||
json_add_string "confirm" "string"
|
||||
json_close_object
|
||||
json_add_object "restart_service"
|
||||
json_close_object
|
||||
json_dump
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
reload)
|
||||
# Reload luci-zerotier service - SAFE: specific operation
|
||||
json_init
|
||||
|
||||
# Validate that the service exists before reloading
|
||||
if [ ! -f "/etc/init.d/luci-zerotier" ]; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "stdout" ""
|
||||
json_add_string "stderr" "luci-zerotier service not found"
|
||||
else
|
||||
output=$(/etc/init.d/luci-zerotier reload 2>&1)
|
||||
exit_code=$?
|
||||
|
||||
json_add_int "code" "$exit_code"
|
||||
json_add_string "stdout" "$output"
|
||||
json_add_string "stderr" ""
|
||||
fi
|
||||
|
||||
json_dump
|
||||
;;
|
||||
status)
|
||||
# Read-only: one liveness flag plus the node address, so the
|
||||
# settings page needs a single RPC instead of two.
|
||||
#
|
||||
# The check is pid + /proc/<pid>/comm rather than
|
||||
# `ps | grep zerotier-one`: comm confirms the pid was not
|
||||
# recycled by an unrelated process, and both cat and read are
|
||||
# builtins, so this costs zero forks. `uci get`/`uci show`
|
||||
# results were also dropped -- nothing ever rendered them.
|
||||
json_init
|
||||
|
||||
_pid=$(cat /var/lib/zerotier-one/zerotier-one.pid 2>/dev/null)
|
||||
if [ -n "$_pid" ] && read -r _comm < "/proc/$_pid/comm" 2>/dev/null && [ "$_comm" = "zerotier-one" ]; then
|
||||
json_add_boolean "running" 1
|
||||
else
|
||||
json_add_boolean "running" 0
|
||||
fi
|
||||
|
||||
if command -v zerotier-cli >/dev/null 2>&1; then
|
||||
_id=$(zerotier-cli -j info 2>/dev/null | jsonfilter -e '@.address' 2>/dev/null)
|
||||
json_add_string "identity" "$_id"
|
||||
else
|
||||
json_add_string "identity" ""
|
||||
fi
|
||||
|
||||
json_dump
|
||||
;;
|
||||
get_networks_peers)
|
||||
# Merged on purpose: zerotier-cli is the 1.2 MB zerotier-one
|
||||
# binary, so every call loads it and opens a connection to the
|
||||
# local control port. The info page polls both data sets on the
|
||||
# same 5s timer, so one method halves the process churn.
|
||||
json_init
|
||||
|
||||
if ! command -v zerotier-cli >/dev/null 2>&1; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "networks" ""
|
||||
json_add_string "peers" ""
|
||||
json_add_string "stderr" "zerotier-cli not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
_networks=$(zerotier-cli -j listnetworks 2>/dev/null)
|
||||
_rc_n=$?
|
||||
_peers=$(zerotier-cli -j listpeers 2>/dev/null)
|
||||
_rc_p=$?
|
||||
|
||||
# Report failure if either half failed, so the UI can tell
|
||||
# "service unreachable" apart from "nothing joined".
|
||||
if [ "$_rc_n" -ne 0 ]; then
|
||||
_rc=$_rc_n
|
||||
else
|
||||
_rc=$_rc_p
|
||||
fi
|
||||
|
||||
json_add_int "code" "$_rc"
|
||||
json_add_string "networks" "$_networks"
|
||||
json_add_string "peers" "$_peers"
|
||||
json_add_string "stderr" ""
|
||||
json_dump
|
||||
;;
|
||||
list_moons)
|
||||
# Read-only: the daemon's own /moon control plane via
|
||||
# `zerotier-cli listmoons`. Returns the raw JSON so the
|
||||
# frontend can normalize the 16-digit world ids and read
|
||||
# the per-moon `waiting` flag itself.
|
||||
json_init
|
||||
|
||||
if ! command -v zerotier-cli >/dev/null 2>&1; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "moons" ""
|
||||
json_add_string "persisted" ""
|
||||
json_add_string "stderr" "zerotier-cli not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
_out=$(zerotier-cli listmoons 2>&1)
|
||||
_rc=$?
|
||||
json_add_int "code" "$_rc"
|
||||
json_add_string "moons" "$_out"
|
||||
json_add_string "persisted" "$(moon_persisted_list | tr '\n' ' ')"
|
||||
json_add_string "stderr" ""
|
||||
json_dump
|
||||
;;
|
||||
orbit_moon)
|
||||
# Orbit a moon. The seed must be the address of one of the
|
||||
# moon's roots; the node fetches the signed world from it and
|
||||
# only then persists moons.d/<id>.moon. Until that happens the
|
||||
# orbit lives purely in RAM (Topology::_moonSeeds is never
|
||||
# persisted) and does not survive a daemon restart -- the
|
||||
# daemon's `waiting:true` flag reports exactly that state and
|
||||
# the UI surfaces it.
|
||||
read -r input
|
||||
json_load "$input" 2>/dev/null
|
||||
json_get_var _mid moon_id 2>/dev/null
|
||||
json_get_var _seed seed 2>/dev/null
|
||||
unset input
|
||||
|
||||
json_init
|
||||
|
||||
if ! command -v zerotier-cli >/dev/null 2>&1; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "moon_id" ""
|
||||
json_add_boolean "orbited" 0
|
||||
json_add_string "stderr" "zerotier-cli not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
_nid=$(moon_norm_id "$_mid")
|
||||
if [ -z "$_nid" ]; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "moon_id" ""
|
||||
json_add_boolean "orbited" 0
|
||||
json_add_string "stderr" "Invalid Moon ID: expected hex digits (10, or the 16-digit padded form), not the zero address"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
_sid=$(moon_norm_id "$_seed")
|
||||
if [ -z "$_sid" ]; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "moon_id" "$_nid"
|
||||
json_add_boolean "orbited" 0
|
||||
json_add_string "stderr" "Invalid Moon Seed: use the 10-digit address of one of the moon's roots (a zero seed is silently ignored by zerotier-cli)"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
_out=$(zerotier-cli orbit "$_nid" "$_sid" 2>&1)
|
||||
_rc=$?
|
||||
|
||||
if moon_wait_listed "$_nid" 6 2; then
|
||||
# Definition arrived: the moon is fully in effect and
|
||||
# moons.d/<id>.moon now exists, so it can be persisted.
|
||||
json_add_int "code" 0
|
||||
json_add_boolean "orbited" 1
|
||||
json_add_boolean "pending" 0
|
||||
json_add_string "stderr" ""
|
||||
/usr/bin/zerotier-sync.sh >/dev/null 2>&1
|
||||
elif [ "$_rc" -eq 0 ]; then
|
||||
# The orbit itself SUCCEEDED. Topology::addMoon always
|
||||
# records a non-zero seed in _moonSeeds, so the daemon has
|
||||
# accepted it; only the signed world definition is still
|
||||
# in flight (it arrives out-of-band from the root, which
|
||||
# can take a while behind a relay or an unseen NAT).
|
||||
# Reporting this as a failure would be wrong: the moon is
|
||||
# already orbited. It is just not persistent yet -- which
|
||||
# is exactly what the `waiting` state means.
|
||||
json_add_int "code" 0
|
||||
json_add_boolean "orbited" 1
|
||||
json_add_boolean "pending" 1
|
||||
json_add_string "stderr" ""
|
||||
else
|
||||
json_add_int "code" 1
|
||||
json_add_boolean "orbited" 0
|
||||
json_add_boolean "pending" 0
|
||||
json_add_string "stderr" "zerotier-cli could not reach the ZeroTier service (rc=$_rc${_out:+, out=$_out})"
|
||||
fi
|
||||
json_add_string "moon_id" "$_nid"
|
||||
json_dump
|
||||
;;
|
||||
deorbit_moon)
|
||||
# `zerotier-cli deorbit` returns "200 deorbit OK" even for a
|
||||
# moon that was never orbited (Topology::removeMoon just
|
||||
# iterates the current set). So: snapshot, deorbit, re-check,
|
||||
# and only report success when the moon actually disappeared.
|
||||
read -r input
|
||||
json_load "$input" 2>/dev/null
|
||||
json_get_var _mid moon_id 2>/dev/null
|
||||
unset input
|
||||
|
||||
json_init
|
||||
|
||||
if ! command -v zerotier-cli >/dev/null 2>&1; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "moon_id" ""
|
||||
json_add_boolean "deorbited" 0
|
||||
json_add_string "stderr" "zerotier-cli not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
_nid=$(moon_norm_id "$_mid")
|
||||
if [ -z "$_nid" ]; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "moon_id" ""
|
||||
json_add_boolean "deorbited" 0
|
||||
json_add_string "stderr" "Invalid Moon ID: expected hex digits (10, or the 16-digit padded form), not the zero address"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
_before=$(zerotier-cli listmoons 2>/dev/null)
|
||||
if ! moon_is_listed "$_nid" "$_before"; then
|
||||
json_add_int "code" 0
|
||||
json_add_boolean "deorbited" 0
|
||||
json_add_string "stderr" "moon $_nid is not orbited"
|
||||
json_add_string "moon_id" "$_nid"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
_out=$(zerotier-cli deorbit "$_nid" 2>&1)
|
||||
_rc=$?
|
||||
_after=$(zerotier-cli listmoons 2>/dev/null)
|
||||
|
||||
if moon_is_listed "$_nid" "$_after"; then
|
||||
json_add_int "code" 1
|
||||
json_add_boolean "deorbited" 0
|
||||
json_add_string "stderr" "moon still listed after deorbit (rc=$_rc${_out:+, out=$_out})"
|
||||
else
|
||||
json_add_int "code" 0
|
||||
json_add_boolean "deorbited" 1
|
||||
json_add_string "stderr" ""
|
||||
# deorbit unlinks moons.d/<id>.moon; mirror the removal
|
||||
# into the persistent dir so it does not come back.
|
||||
/usr/bin/zerotier-sync.sh >/dev/null 2>&1
|
||||
fi
|
||||
json_add_string "moon_id" "$_nid"
|
||||
json_dump
|
||||
;;
|
||||
ping_networks)
|
||||
json_init
|
||||
|
||||
# 并发锁:防止多个 ping_networks 同时执行(多标签页、ubus 直调等)。
|
||||
# noclobber (set -C) 原子创建,消除 check-then-create 竞态。
|
||||
# 锁文件写入 "$PID $DEADLINE"(绝对期限,随扫描规模重算)。
|
||||
# 双重校验:PID 还活着(kill -0)且未过 DEADLINE —— 兜底进程
|
||||
# 暴毙(SIGKILL)后的锁复活。
|
||||
LOCKFILE="/tmp/zt_ping.lock"
|
||||
_lock_max=120
|
||||
_lock_try() {
|
||||
( set -C; echo "$$ $(( $(date +%s) + _lock_max ))" > "$LOCKFILE" ) 2>/dev/null
|
||||
}
|
||||
_lock_busy() {
|
||||
json_add_int "code" 1
|
||||
json_add_string "result" ""
|
||||
json_add_string "stderr" "scan already in progress"
|
||||
json_dump
|
||||
}
|
||||
# 只释放本进程持有的锁:扫描超过 deadline 后锁可能已被其他进程
|
||||
# 判定过期并重建,无条件 rm 会破坏新锁,放任第三个并发扫描
|
||||
_lock_release() {
|
||||
if [ -f "$LOCKFILE" ]; then
|
||||
read -r _lp _ld < "$LOCKFILE" 2>/dev/null
|
||||
[ "$_lp" = "$$" ] && rm -f "$LOCKFILE"
|
||||
fi
|
||||
}
|
||||
if ! _lock_try; then
|
||||
read -r _lp _ld < "$LOCKFILE" 2>/dev/null
|
||||
if [ -n "$_lp" ] && kill -0 "$_lp" 2>/dev/null && [ "$(date +%s)" -lt "${_ld:-0}" ]; then
|
||||
_lock_busy
|
||||
return
|
||||
fi
|
||||
rm -f "$LOCKFILE"
|
||||
if ! _lock_try; then
|
||||
_lock_busy
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
tmpfile=$(mktemp /tmp/zt_ping_XXXXXX)
|
||||
_sem=""
|
||||
trap '_lock_release; rm -f "$tmpfile" "$_sem" "$LOCKFILE.$$.tmp"; exec 9>&- 2>/dev/null' EXIT
|
||||
trap 'exit 1' INT TERM
|
||||
|
||||
if ! command -v ping >/dev/null 2>&1; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "result" ""
|
||||
json_add_string "stderr" "ping not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
if ! command -v zerotier-cli >/dev/null 2>&1; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "result" ""
|
||||
json_add_string "stderr" "zerotier-cli not found"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Extract assigned IP addresses via JSON output (reliable field names
|
||||
# instead of fragile column-position parsing of text output)
|
||||
ip_list=$(zerotier-cli -j listnetworks 2>/dev/null | jsonfilter -e '$[*].assignedAddresses[*]' 2>/dev/null)
|
||||
if [ -z "$ip_list" ]; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "result" ""
|
||||
json_add_string "stderr" "no assigned addresses or zerotier-cli failed"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# 已知扫描规模后重算锁期限(每个 v4 网段约 60s 最差耗时 + 60s 余量),
|
||||
# 覆盖多网络/低并发场景,避免锁被误判过期
|
||||
_nnets=0
|
||||
for _ipc in $ip_list; do
|
||||
case "$_ipc" in
|
||||
*.*.*.*/*) _nnets=$((_nnets + 1)) ;;
|
||||
esac
|
||||
done
|
||||
_lock_max=$((60 + _nnets * 60))
|
||||
# 临时文件 + rename 原子更新期限:直接 > 覆写存在被并发申请者
|
||||
# 读到空文件并误判锁过期的窗口
|
||||
echo "$$ $(( $(date +%s) + _lock_max ))" > "$LOCKFILE.$$.tmp" && mv -f "$LOCKFILE.$$.tmp" "$LOCKFILE"
|
||||
|
||||
# 并发限流:FIFO 信号量模式。
|
||||
# 预先向命名管道写入 MAX_RUNNING 个 token(换行符);ping_one() 每次
|
||||
# read -u 9 消费一个 token(管道空时自动阻塞,不消耗 CPU);后台子 shell
|
||||
# 在 ping 结束后 echo >&9 归还 token。
|
||||
#
|
||||
# 效果:始终精确保持 MAX_RUNNING 个并发 ping。一个快 ping(100ms 完成)
|
||||
# 立刻让出 slot 给下一个 IP,不像 batch 模式那样要等齐一批里最慢的那个。
|
||||
#
|
||||
# 依赖 busybox ash 的 read 逐字节语义(不越过换行符吞掉多个 token)。
|
||||
# OpenWrt 的 /bin/sh 永远是 busybox ash,所以保证可用。
|
||||
# 不依赖 usleep / sleep 分数延时 / xargs -P(这些在 stock OpenWrt
|
||||
# busybox 默认配置下均不可用)。
|
||||
#
|
||||
# MAX_RUNNING 自适应:根据当前可用内存动态计算。
|
||||
# 实测每个 ping slot(subshell + ping 进程)并发时占 ~150 kB RSS
|
||||
# (共享文本段 + COW 后的实际物理内存)。按 300 kB/slot(2x 安全余量)、
|
||||
# 仅使用 1/3 可用内存计算,范围 [16, 512]。
|
||||
_avail_kb=$(awk '/^MemAvailable:/ {print $2}' /proc/meminfo 2>/dev/null)
|
||||
if [ -n "$_avail_kb" ] && [ "$_avail_kb" -gt 0 ]; then
|
||||
MAX_RUNNING=$((_avail_kb / 3 / 300))
|
||||
[ "$MAX_RUNNING" -lt 16 ] && MAX_RUNNING=16
|
||||
[ "$MAX_RUNNING" -gt 512 ] && MAX_RUNNING=512
|
||||
else
|
||||
MAX_RUNNING=64
|
||||
fi
|
||||
_sem=$(mktemp -u /tmp/zt_ping_sem_XXXXXX)
|
||||
if mkfifo "$_sem" 2>/dev/null; then
|
||||
exec 9<>"$_sem"
|
||||
_i=0
|
||||
while [ "$_i" -lt "$MAX_RUNNING" ]; do
|
||||
echo >&9
|
||||
_i=$((_i + 1))
|
||||
done
|
||||
ping_one() {
|
||||
read -u 9 2>/dev/null
|
||||
(
|
||||
ping -c 2 -W 1 "$1" >/dev/null 2>&1 && echo "OK:$1" || echo "FAIL:$1"
|
||||
echo >&9 2>/dev/null
|
||||
) >> "$tmpfile" 2>&1 &
|
||||
}
|
||||
else
|
||||
rm -f "$_sem" 2>/dev/null; _sem=""
|
||||
ping_one() {
|
||||
( ping -c 2 -W 1 "$1" >/dev/null 2>&1 && echo "OK:$1" || echo "FAIL:$1" ) >> "$tmpfile" 2>&1 &
|
||||
}
|
||||
fi
|
||||
|
||||
# 设计说明:扫描器覆盖典型 ZeroTier 部署场景(/24 ~ /23,最多 512 个主机)。
|
||||
# 对于更大的网段(/22、/21、/16 等),仅扫描当前 IP 所在的 /24,因为:
|
||||
# - /23 已覆盖普通用户使用 zerotier 的实际上限
|
||||
# - 扫描 /22+(1024+ 个 IP)对常规连通性检查属于过度操作
|
||||
# - 大网段用户应改用专门的扫描工具(nmap 等)
|
||||
for ip_cidr in $ip_list; do
|
||||
case "$ip_cidr" in
|
||||
*.*.*.*/*)
|
||||
ip=${ip_cidr%%/*}
|
||||
cidr=${ip_cidr##*/}
|
||||
if ! echo "$ip" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
continue
|
||||
fi
|
||||
# Glob-validate the prefix BEFORE any test/arithmetic:
|
||||
# a non-numeric cidr makes `[ -lt/-gt ]` error out on
|
||||
# both sides (so the range check below would NOT skip
|
||||
# it) and `$((32 - cidr))` would then evaluate as 32,
|
||||
# yielding a ~4-billion-iteration ping loop as root.
|
||||
case "$cidr" in
|
||||
''|*[!0-9]*) continue ;;
|
||||
esac
|
||||
if [ "$cidr" -lt 1 ] || [ "$cidr" -gt 32 ]; then
|
||||
continue
|
||||
fi
|
||||
_oldIFS="$IFS"
|
||||
IFS='.'
|
||||
set -- $ip
|
||||
i1="$1"; i2="$2"; i3="$3"; i4="$4"
|
||||
IFS="$_oldIFS"
|
||||
if [ "$cidr" -le 24 ] && [ "$cidr" -ge 23 ]; then
|
||||
if [ "$cidr" = "23" ]; then
|
||||
base_i3=$((i3 & ~1))
|
||||
for j in $(seq 0 255); do
|
||||
ping_one "$i1.$i2.$base_i3.$j"
|
||||
done
|
||||
_base_i3=$((base_i3 + 1))
|
||||
for j in $(seq 0 255); do
|
||||
ping_one "$i1.$i2.$_base_i3.$j"
|
||||
done
|
||||
else
|
||||
for j in $(seq 0 255); do
|
||||
ping_one "$i1.$i2.$i3.$j"
|
||||
done
|
||||
fi
|
||||
elif [ "$cidr" -lt 23 ]; then
|
||||
# 大网段(/22、/21、/16...)只扫描当前所在 /24,详见上方设计说明
|
||||
for j in $(seq 0 255); do
|
||||
ping_one "$i1.$i2.$i3.$j"
|
||||
done
|
||||
else
|
||||
shifts=$((32 - cidr))
|
||||
host_count=1
|
||||
while [ "$shifts" -gt 0 ]; do
|
||||
host_count=$((host_count * 2))
|
||||
shifts=$((shifts - 1))
|
||||
done
|
||||
net_mask=$((256 - host_count))
|
||||
net_addr=$((i4 & net_mask))
|
||||
for j in $(seq "$net_addr" "$((net_addr + host_count - 1))"); do
|
||||
ping_one "$i1.$i2.$i3.$j"
|
||||
done
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
wait
|
||||
|
||||
# Release semaphore fd and clean up
|
||||
[ -n "$_sem" ] && exec 9>&- 2>/dev/null
|
||||
rm -f "$_sem" 2>/dev/null
|
||||
|
||||
if [ -f "$tmpfile" ]; then
|
||||
result=$(sort -u "$tmpfile" | tr '\n' '|')
|
||||
rm -f "$tmpfile"
|
||||
else
|
||||
result="no_result"
|
||||
fi
|
||||
|
||||
json_add_int "code" 0
|
||||
json_add_string "result" "$result"
|
||||
json_add_string "stderr" ""
|
||||
json_dump
|
||||
;;
|
||||
sync_config)
|
||||
# Persist runtime state to the config dir - SAFE: specific operation
|
||||
#
|
||||
# 可选参数 local_conf_path:表单里尚未提交的新值(保存并应用时
|
||||
# 由 general.js 传入);缺省时回退到已提交的配置值(Backup Now
|
||||
# 按钮路径)。参数经 stdin 以 JSON 传入(rpcd shell 插件惯例)。
|
||||
read -r input
|
||||
json_load "$input" 2>/dev/null
|
||||
json_get_var _lc local_conf_path 2>/dev/null
|
||||
[ -n "$_lc" ] || _lc=$(uci -q get zerotier.global.local_conf_path 2>/dev/null)
|
||||
|
||||
json_init
|
||||
|
||||
output=$(/usr/bin/zerotier-sync.sh 2>&1)
|
||||
exit_code=$?
|
||||
|
||||
# 收敛 local_conf_path 到持久化配置目录:上游 init 用裸 `ln -s`
|
||||
# 把它链入运行目录,当 config_path 已提供 local.conf(本应用会播种)
|
||||
# 时静默失败。改为把文件内容导入 config_path/local.conf,上游的
|
||||
# cp -r 自然会带上正确内容,ln -s 失败也无害。
|
||||
# 必须在 sync 之后执行,避免被运行目录里的旧拷贝覆盖。
|
||||
if [ -n "$_lc" ] && [ -f "$_lc" ]; then
|
||||
_cp=$(uci -q get zerotier.global.config_path 2>/dev/null)
|
||||
[ -n "$_cp" ] || _cp="/etc/zerotier"
|
||||
[ "$_lc" != "$_cp/local.conf" ] && cp -f "$_lc" "$_cp/local.conf" 2>/dev/null
|
||||
fi
|
||||
|
||||
json_add_int "code" "$exit_code"
|
||||
json_add_string "stdout" "$output"
|
||||
json_add_string "stderr" ""
|
||||
json_dump
|
||||
;;
|
||||
restart_service)
|
||||
# Restart ZeroTier service - SAFE: specific operation
|
||||
json_init
|
||||
|
||||
# Validate that the service exists
|
||||
if [ ! -f "/etc/init.d/zerotier" ]; then
|
||||
json_add_int "code" 1
|
||||
json_add_string "stdout" ""
|
||||
json_add_string "stderr" "zerotier service not found"
|
||||
else
|
||||
# Persist runtime state first: the zerotier init's stop_service
|
||||
# does rm -rf /var/lib/zerotier-one, discarding anything not yet
|
||||
# synced (CLI-joined networks, controller/moon data)
|
||||
/usr/bin/zerotier-sync.sh >/dev/null 2>&1
|
||||
|
||||
# Restart the main zerotier service
|
||||
output=$(/etc/init.d/zerotier restart 2>&1)
|
||||
exit_code=$?
|
||||
|
||||
json_add_int "code" "$exit_code"
|
||||
json_add_string "stdout" "$output"
|
||||
json_add_string "stderr" ""
|
||||
fi
|
||||
|
||||
json_dump
|
||||
;;
|
||||
remote_list)
|
||||
# Read-only: enumerate configured remote hosts. Key material is
|
||||
# never returned -- only whether a key file exists.
|
||||
json_init
|
||||
json_add_array "hosts"
|
||||
for _s in $(uci -q show zerotier 2>/dev/null | sed -n 's/^zerotier\.\([^.=]*\)=remote$/\1/p'); do
|
||||
_h=$(uci -q get "zerotier.$_s.host")
|
||||
[ -n "$_h" ] || continue
|
||||
_kp=$(uci -q get "zerotier.$_s.key_path")
|
||||
[ -n "$_kp" ] || _kp="/etc/zerotier/remote/$_s.key"
|
||||
json_add_object ""
|
||||
json_add_string "section" "$_s"
|
||||
json_add_string "name" "$(uci -q get "zerotier.$_s.name" || echo "$_s")"
|
||||
json_add_string "host" "$_h"
|
||||
json_add_string "port" "$(uci -q get "zerotier.$_s.port" || echo 22)"
|
||||
json_add_string "user" "$(uci -q get "zerotier.$_s.user" || echo root)"
|
||||
json_add_string "controller_port" "$(uci -q get "zerotier.$_s.controller_port" || echo 9993)"
|
||||
json_add_string "key_path" "$_kp"
|
||||
if [ -f "$_kp" ]; then
|
||||
json_add_boolean "key_present" 1
|
||||
else
|
||||
json_add_boolean "key_present" 0
|
||||
fi
|
||||
json_close_object
|
||||
done
|
||||
json_close_array
|
||||
json_dump
|
||||
;;
|
||||
remote_host_set)
|
||||
# The private key is written to a 0600 file, never into UCI:
|
||||
# /etc/config is world-readable and is swept into sysupgrade
|
||||
# backups, whereas a key file can be excluded from keep.d.
|
||||
read -r input
|
||||
json_load "$input" 2>/dev/null
|
||||
json_get_var _sect section 2>/dev/null
|
||||
json_get_var _hname name 2>/dev/null
|
||||
json_get_var _rhost host 2>/dev/null
|
||||
json_get_var _rport port 2>/dev/null
|
||||
json_get_var _ruser user 2>/dev/null
|
||||
json_get_var _cport controller_port 2>/dev/null
|
||||
json_get_var _pkey private_key 2>/dev/null
|
||||
unset input
|
||||
|
||||
json_init
|
||||
|
||||
case "$_sect" in
|
||||
''|*[!A-Za-z0-9_]*) _err="invalid section name" ;;
|
||||
*) _err="" ;;
|
||||
esac
|
||||
[ -z "$_err" ] || { json_add_int "code" 1; json_add_string "error" "$_err"; json_dump; return; }
|
||||
|
||||
case "$_rhost" in
|
||||
''|*[!A-Za-z0-9.:_-]*) _err="host must be a hostname or IP literal" ;;
|
||||
*) case "$_rhost" in *[!0-9A-Za-z]*) _err="" ;; *) _err="host must contain a letter or digit" ;; esac ;;
|
||||
esac
|
||||
[ -z "$_err" ] || { json_add_int "code" 1; json_add_string "error" "$_err"; json_dump; return; }
|
||||
|
||||
case "$_ruser" in
|
||||
''|[a-zA-Z_]*) case "$_ruser" in *[!a-zA-Z0-9_-]*) _err="invalid ssh user" ;; esac ;;
|
||||
*) _err="invalid ssh user" ;;
|
||||
esac
|
||||
[ -z "$_err" ] || { json_add_int "code" 1; json_add_string "error" "$_err"; json_dump; return; }
|
||||
|
||||
for _p in "$_rport" "$_cport"; do
|
||||
case "$_p" in
|
||||
''|*[!0-9]*) _err="ports must be numeric" ;;
|
||||
*) [ "$_p" -ge 1 ] 2>/dev/null && [ "$_p" -le 65535 ] 2>/dev/null || _err="port out of range" ;;
|
||||
esac
|
||||
[ -z "$_err" ] || { json_add_int "code" 1; json_add_string "error" "$_err"; json_dump; return; }
|
||||
done
|
||||
|
||||
_rport=${_rport:-22}
|
||||
_ruser=${_ruser:-root}
|
||||
_cport=${_cport:-9993}
|
||||
_hname=${_hname:-$_sect}
|
||||
_kp="/etc/zerotier/remote/$_sect.key"
|
||||
|
||||
mkdir -p /etc/zerotier/remote || { json_add_int "code" 1; json_add_string "error" "cannot create /etc/zerotier/remote"; json_dump; return; }
|
||||
chmod 700 /etc/zerotier/remote 2>/dev/null
|
||||
|
||||
# An existing key is preserved when the field is left blank, so
|
||||
# editing a host's name does not silently destroy its key.
|
||||
if [ -n "$_pkey" ]; then
|
||||
case "$_pkey" in
|
||||
*"PRIVATE KEY"*) ;;
|
||||
*) json_add_int "code" 1; json_add_string "error" "that does not look like a private key"; json_dump; return ;;
|
||||
esac
|
||||
(umask 077; printf '%s\n' "$_pkey" > "$_kp") || { json_add_int "code" 1; json_add_string "error" "could not write the key file"; json_dump; return; }
|
||||
chmod 600 "$_kp" 2>/dev/null
|
||||
fi
|
||||
|
||||
uci -q delete "zerotier.$_sect" 2>/dev/null
|
||||
uci set "zerotier.$_sect=remote"
|
||||
uci set "zerotier.$_sect.name=$_hname"
|
||||
uci set "zerotier.$_sect.host=$_rhost"
|
||||
uci set "zerotier.$_sect.port=$_rport"
|
||||
uci set "zerotier.$_sect.user=$_ruser"
|
||||
uci set "zerotier.$_sect.controller_port=$_cport"
|
||||
uci set "zerotier.$_sect.key_path=$_kp"
|
||||
uci commit zerotier || { json_add_int "code" 1; json_add_string "error" "uci commit failed"; json_dump; return; }
|
||||
|
||||
json_add_int "code" 0
|
||||
json_add_string "key_path" "$_kp"
|
||||
json_dump
|
||||
;;
|
||||
remote_host_del)
|
||||
read -r input
|
||||
json_load "$input" 2>/dev/null
|
||||
json_get_var _sect section 2>/dev/null
|
||||
unset input
|
||||
|
||||
json_init
|
||||
case "$_sect" in
|
||||
''|*[!A-Za-z0-9_]*) json_add_int "code" 1; json_add_string "error" "invalid section name"; json_dump; return ;;
|
||||
esac
|
||||
_kp=$(uci -q get "zerotier.$_sect.key_path")
|
||||
[ -n "$_kp" ] || _kp="/etc/zerotier/remote/$_sect.key"
|
||||
# Only unlink inside our own managed directory.
|
||||
case "$_kp" in
|
||||
/etc/zerotier/remote/*.key) [ -f "$_kp" ] && rm -f "$_kp" ;;
|
||||
esac
|
||||
uci -q delete "zerotier.$_sect"
|
||||
uci commit zerotier
|
||||
json_add_int "code" 0
|
||||
json_dump
|
||||
;;
|
||||
remote_diagnose|remote_ctl_get|remote_member_list|remote_peer_list|remote_network_set|remote_network_del|remote_member_set|remote_moon_plan|remote_moon_apply)
|
||||
# Thin delegation to zerotier-remote.sh, which owns all SSH,
|
||||
# tunnel and validation logic. Its stdout is already the JSON we
|
||||
# return verbatim, so nothing is re-encoded here.
|
||||
read -r input
|
||||
json_load "$input" 2>/dev/null
|
||||
json_get_var _sect section 2>/dev/null
|
||||
json_get_var _path path 2>/dev/null
|
||||
json_get_var _nwid nwid 2>/dev/null
|
||||
json_get_var _body body 2>/dev/null
|
||||
json_get_var _mid member_id 2>/dev/null
|
||||
json_get_var _cf confirm 2>/dev/null
|
||||
unset input
|
||||
|
||||
case "$2" in
|
||||
remote_diagnose) _args="diagnose $_sect" ;;
|
||||
remote_ctl_get) _args="ctl-get $_sect $_path" ;;
|
||||
remote_member_list) _args="member-list $_sect $_nwid" ;;
|
||||
remote_peer_list) _args="peer-list $_sect" ;;
|
||||
remote_network_set) _args="ctl-network-set $_sect $_nwid $_body" ;;
|
||||
remote_network_del) _args="ctl-network-del $_sect $_nwid" ;;
|
||||
remote_member_set) _args="ctl-member-set $_sect $_nwid $_mid $_body" ;;
|
||||
remote_moon_plan) _args="moon-plan $_sect" ;;
|
||||
remote_moon_apply) _args="moon-apply $_sect $_cf" ;;
|
||||
esac
|
||||
|
||||
if [ ! -x /usr/bin/zerotier-remote.sh ]; then
|
||||
json_init
|
||||
json_add_int "code" 1
|
||||
json_add_string "error" "zerotier-remote.sh is missing"
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
|
||||
# Intentional word splitting: _args is a command line built
|
||||
# from validated fields, and the helper re-validates each one.
|
||||
# shellcheck disable=SC2086
|
||||
_out=$(/usr/bin/zerotier-remote.sh $_args 2>/dev/null)
|
||||
_rc=$?
|
||||
if [ -z "$_out" ]; then
|
||||
json_init
|
||||
json_add_int "code" 1
|
||||
if [ "$_rc" -eq 124 ]; then
|
||||
json_add_string "error" "the remote host did not respond in time"
|
||||
else
|
||||
json_add_string "error" "remote helper failed (rc=$_rc)"
|
||||
fi
|
||||
json_dump
|
||||
return
|
||||
fi
|
||||
printf '%s\n' "$_out"
|
||||
;;
|
||||
*)
|
||||
# Invalid method - return error
|
||||
json_init
|
||||
json_add_int "code" 1
|
||||
json_add_string "stdout" ""
|
||||
json_add_string "stderr" "Invalid method: $2"
|
||||
json_dump
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
reload_service() {
|
||||
restart
|
||||
}
|
||||
Reference in new issue
Block a user