mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-06 03:14:12 +08:00
🚀 Sync 2026-10-06 13:56:25
This commit is contained in:
1 parent
d63f87a572
commit
3b4244f7de
14 files changed
+1034
-410
No files matched your search
+249
@@ -0,0 +1,249 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# rpcd backend for luci-app-ipsec-vpnd.
|
||||
# Exposes one read-only ubus object method "sessions": parse `ipsec statusall`
|
||||
# (stroke), merge the currently-online IKEv1 SA set with a persisted history of
|
||||
# seen clients (router-side, survives browser/reboot), and return the combined
|
||||
# list with an "online" flag per entry.
|
||||
#
|
||||
# rpcd plugin protocol: argv[1] = "list" | "call", argv[2] = method name when
|
||||
# calling; the request JSON arrives on stdin. stdout carries one JSON document.
|
||||
#
|
||||
# Persisted state: one line per known client, TAB separated:
|
||||
# key\tuser\tremote\tvip\tage\tlast_seen (key = "user|remote")
|
||||
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
STATE_FILE="/etc/luci-app-ipsec-vpnd-sessions.state"
|
||||
# Working copy for sessions(); kept in /tmp so an unchanged 5s poll doesn't keep
|
||||
# rewriting the persistent state file on flash. Never share this path with the
|
||||
# updown hook.
|
||||
SESSIONS_TMP="/tmp/.luci-app-ipsec-vpnd-sessions.tmp"
|
||||
LOCK_FILE="/tmp/.luci-app-ipsec-vpnd-sessions.lock"
|
||||
|
||||
# Best-effort advisory lock: serialises the read/merge/write against the updown
|
||||
# hook. If flock is unavailable each writer still uses its own tmp, so the worst
|
||||
# case is a lost update in a tiny race window, never a corrupted file.
|
||||
flock_lock() {
|
||||
command -v flock >/dev/null 2>&1 || return 0
|
||||
exec 9>"$LOCK_FILE" 2>/dev/null || return 0
|
||||
flock -w 5 9 2>/dev/null || return 0
|
||||
}
|
||||
flock_unlock() {
|
||||
command -v flock >/dev/null 2>&1 || return 0
|
||||
flock -u 9 2>/dev/null
|
||||
exec 9>&- 2>/dev/null
|
||||
}
|
||||
|
||||
list_methods() {
|
||||
printf '%s\n' '{"sessions":{},"clear_history":{}}'
|
||||
}
|
||||
|
||||
json_err() {
|
||||
json_init
|
||||
json_add_int code 1
|
||||
json_add_string error "$1"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# Parse `ipsec statusall` from stdin into TAB-separated online rows:
|
||||
# key\tuser\tremote\tvip\tage
|
||||
parse_statusall() {
|
||||
awk '
|
||||
function getsa(l) {
|
||||
if (match(l, /^[ \t]*[A-Za-z0-9_.-]+\[[0-9]+\]:/)) {
|
||||
s = substr(l, RSTART, RLENGTH - 1)
|
||||
sub(/^[ \t]*/, "", s)
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
function getts(s) {
|
||||
if (match(s, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\/[0-9]+/))
|
||||
return substr(s, RSTART, RLENGTH)
|
||||
return ""
|
||||
}
|
||||
BEGIN { n = 0; last = "" }
|
||||
{
|
||||
line = $0
|
||||
|
||||
if (match(line, /^[ \t]*[A-Za-z0-9_.-]+\[[0-9]+\]: ESTABLISHED /)) {
|
||||
sa = getsa(line)
|
||||
if (!(sa in est)) {
|
||||
est[sa] = 1
|
||||
order[++n] = sa
|
||||
last = sa
|
||||
|
||||
s = index(line, "ESTABLISHED ") + 12
|
||||
e = index(line, " ago,")
|
||||
age[sa] = (e > s) ? substr(line, s, e - s) : ""
|
||||
|
||||
rest = ""
|
||||
i = index(line, "...")
|
||||
if (i > 0) rest = substr(line, i + 3)
|
||||
ip = ""
|
||||
if (match(rest, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/))
|
||||
ip = substr(rest, RSTART, RLENGTH)
|
||||
if (ip == "") {
|
||||
sub(/^[ \t]*/, "", rest)
|
||||
j = index(rest, "[")
|
||||
if (j > 0) rest = substr(rest, 1, j - 1)
|
||||
ip = rest
|
||||
}
|
||||
remote[sa] = ip
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
if (line ~ /[Xx][Aa]uth/ && index(line, "identity:") > 0) {
|
||||
sa = getsa(line)
|
||||
if (sa != "" && (sa in est) && !(sa in user)) {
|
||||
v = substr(line, index(line, "identity:") + 9)
|
||||
gsub(/^[ \t"'"'"']+|[ \t"'"'"']+$/, "", v)
|
||||
if (v != "") user[sa] = v
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
# CHILD_SA traffic selector carrying the assigned /32 client address;
|
||||
# strongSwan 6.x puts the pool /32 on the RIGHT (remote) side here.
|
||||
if (index(line, "===") > 0 && line ~ /\/[0-9]+/) {
|
||||
i = index(line, "===")
|
||||
l = getts(substr(line, 1, i - 1))
|
||||
r = getts(substr(line, i + 3))
|
||||
ip = ""
|
||||
if (r ~ /\/32$/) ip = substr(r, 1, length(r) - 3)
|
||||
else if (l ~ /\/32$/) ip = substr(l, 1, length(l) - 3)
|
||||
if (ip != "") {
|
||||
sa = getsa(line)
|
||||
if (!(sa in est)) sa = last
|
||||
if ((sa in est) && !(sa in vip)) vip[sa] = ip
|
||||
}
|
||||
next
|
||||
}
|
||||
}
|
||||
END {
|
||||
for (k = 1; k <= n; k++) {
|
||||
sa = order[k]
|
||||
u = user[sa]; r = remote[sa]; v = vip[sa]; a = age[sa]
|
||||
if (u == "") u = "-"
|
||||
if (r == "") r = "-"
|
||||
printf "%s|%s\t%s\t%s\t%s\t%s\n", u, r, u, r, v, a
|
||||
}
|
||||
}'
|
||||
}
|
||||
|
||||
sessions() {
|
||||
local now="$(date +%s)"
|
||||
local online_tmp="/tmp/.luci-app-ipsec-vpnd-online.tsv"
|
||||
|
||||
# ensure the history file exists so awk can always read it as first input
|
||||
[ -f "$STATE_FILE" ] || : > "$STATE_FILE"
|
||||
|
||||
# current online clients as TSV
|
||||
/usr/sbin/ipsec statusall 2>/dev/null | parse_statusall > "$online_tmp"
|
||||
|
||||
# serialize read/merge/write with the updown hook
|
||||
flock_lock
|
||||
|
||||
awk -F'\t' -v now="$now" -v state_tmp="$SESSIONS_TMP" -v state="$STATE_FILE" '
|
||||
function jesc(s) {
|
||||
gsub(/\\/, "\\\\", s)
|
||||
gsub(/"/, "\\\"", s)
|
||||
return s
|
||||
}
|
||||
BEGIN { n = 0 }
|
||||
FILENAME == state {
|
||||
# history rows: key user remote vip age last_seen
|
||||
key = $1
|
||||
if (key == "") next
|
||||
if (!(key in seen)) { seen[key] = 1; order[++n] = key }
|
||||
hu[key] = $2; hr[key] = $3; hv[key] = $4; ha[key] = $5; hl[key] = $6
|
||||
next
|
||||
}
|
||||
{
|
||||
# online rows: key user remote vip age
|
||||
key = $1
|
||||
if (key == "") next
|
||||
active[key] = 1
|
||||
if (!(key in seen)) { seen[key] = 1; order[++n] = key }
|
||||
hu[key] = $2; hr[key] = $3; hv[key] = $4; ha[key] = $5; hl[key] = now
|
||||
}
|
||||
END {
|
||||
# build output order: online first (kept), then offline by last_seen desc
|
||||
nc = 0
|
||||
for (i = 1; i <= n; i++)
|
||||
if (active[order[i]]) out[++nc] = order[i]
|
||||
oc = 0
|
||||
for (i = 1; i <= n; i++)
|
||||
if (!active[order[i]]) off[++oc] = order[i]
|
||||
for (i = 1; i <= oc; i++)
|
||||
for (j = i + 1; j <= oc; j++)
|
||||
if ((hl[off[j]] + 0) > (hl[off[i]] + 0)) {
|
||||
t = off[i]; off[i] = off[j]; off[j] = t
|
||||
}
|
||||
for (i = 1; i <= oc; i++) out[++nc] = off[i]
|
||||
|
||||
# JSON response
|
||||
printf "{\"code\":0,\"sessions\":["
|
||||
first = 1
|
||||
for (i = 1; i <= nc; i++) {
|
||||
k = out[i]
|
||||
if (!first) printf ","
|
||||
first = 0
|
||||
printf "{\"user\":\"%s\",\"remote\":\"%s\",\"vip\":\"%s\",\"age\":\"%s\",\"online\":%s}",
|
||||
jesc(hu[k]), jesc(hr[k]), jesc(hv[k]), jesc(ha[k]),
|
||||
(active[k] ? "true" : "false")
|
||||
}
|
||||
printf "]}\n"
|
||||
|
||||
# rewrite persisted history (drop online flag; recomputed each call)
|
||||
for (i = 1; i <= n; i++) {
|
||||
k = order[i]
|
||||
printf "%s\t%s\t%s\t%s\t%s\t%s\n", k, hu[k], hr[k], hv[k], ha[k], hl[k] > state_tmp
|
||||
}
|
||||
}' "$STATE_FILE" "$online_tmp"
|
||||
|
||||
rc=$?
|
||||
rm -f "$online_tmp"
|
||||
# rewrite the persisted history only when it actually changed, so an idle
|
||||
# 5s poll doesn't keep re-writing the state file on flash.
|
||||
if [ -s "$SESSIONS_TMP" ] && ! cmp -s "$SESSIONS_TMP" "$STATE_FILE"; then
|
||||
mv "$SESSIONS_TMP" "$STATE_FILE"
|
||||
fi
|
||||
rm -f "$SESSIONS_TMP"
|
||||
flock_unlock
|
||||
return $rc
|
||||
}
|
||||
|
||||
clear_history() {
|
||||
flock_lock
|
||||
rm -f "$STATE_FILE" "$SESSIONS_TMP"
|
||||
flock_unlock
|
||||
json_init
|
||||
json_add_int code 0
|
||||
json_add_string message "cleared"
|
||||
json_dump
|
||||
}
|
||||
|
||||
main() {
|
||||
case "$1" in
|
||||
list)
|
||||
list_methods
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
sessions) sessions ;;
|
||||
clear_history) clear_history ;;
|
||||
*) json_err "unknown method" ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' '{}'
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# source-able for offline parser testing
|
||||
[ -n "$IPSEC_VPND_SKIP_MAIN" ] || main "$@"
|
||||
Reference in new issue
Block a user