mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-29 08:45:13 +08:00
🎉 Sync 2026-09-27 21:03:26
This commit is contained in:
1 parent
f4559bbed1
commit
065763f7fd
673 files changed
+261285
-34160
No files matched your search
+258
@@ -0,0 +1,258 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=ash
|
||||
|
||||
# Variables below are populated by sourced helpers and ss_load_config()
|
||||
# shellcheck disable=SC2154
|
||||
|
||||
LC_ALL=C
|
||||
IPKG_INSTROOT="${IPKG_INSTROOT:-}"
|
||||
export IPKG_INSTROOT
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. /lib/functions.sh
|
||||
# shellcheck disable=SC1091
|
||||
. /usr/lib/safeshield/core.sh
|
||||
|
||||
readonly SS_REFRESHD_LOCK='/var/lock/safeshield-refreshd.lock'
|
||||
readonly SS_REFRESHD_LOCK_FD=308
|
||||
|
||||
ss_should_terminate=0
|
||||
ss_sleep_pid=''
|
||||
|
||||
refreshd_lock() {
|
||||
mkdir -p "${SS_REFRESHD_LOCK%/*}" || return 1
|
||||
eval "exec ${SS_REFRESHD_LOCK_FD}>\"${SS_REFRESHD_LOCK}\"" || return 1
|
||||
flock -n "${SS_REFRESHD_LOCK_FD}" || return 1
|
||||
}
|
||||
|
||||
refreshd_unlock() {
|
||||
flock -u "${SS_REFRESHD_LOCK_FD}" 2>/dev/null || true
|
||||
eval "exec ${SS_REFRESHD_LOCK_FD}>&-" 2>/dev/null || true
|
||||
}
|
||||
|
||||
handle_term() {
|
||||
ss_should_terminate=1
|
||||
if [ -n "$ss_sleep_pid" ]; then
|
||||
kill "$ss_sleep_pid" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
sleep_seconds() {
|
||||
local seconds="$1"
|
||||
local rc=0
|
||||
|
||||
[ "$ss_should_terminate" -eq 1 ] && return 1
|
||||
[ "$seconds" -gt 0 ] 2>/dev/null || return 0
|
||||
|
||||
sleep "$seconds" &
|
||||
ss_sleep_pid=$!
|
||||
wait "$ss_sleep_pid" 2>/dev/null || rc=$?
|
||||
ss_sleep_pid=''
|
||||
|
||||
[ "$ss_should_terminate" -eq 1 ] && return 1
|
||||
[ "$rc" -eq 0 ]
|
||||
}
|
||||
|
||||
wait_for_wan_if_needed() {
|
||||
local require_wan
|
||||
|
||||
require_wan="$(ss_config_get config require_wan 1)"
|
||||
|
||||
case "$require_wan" in
|
||||
1)
|
||||
ubus -t 30 wait_for network.interface.wan 2>/dev/null || true
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
status_epoch() {
|
||||
local key="$1"
|
||||
local value
|
||||
|
||||
value="$(json get "$key" 2>/dev/null)"
|
||||
is_valid_integer "$value" || value=0
|
||||
printf '%s\n' "$value"
|
||||
}
|
||||
|
||||
schedule_base_epoch() {
|
||||
local now="$1"
|
||||
local last_success last_attempt base=0
|
||||
|
||||
last_success="$(status_epoch last_success)"
|
||||
last_attempt="$(status_epoch last_attempt)"
|
||||
|
||||
if [ "$last_success" -gt 0 ] 2>/dev/null && [ "$last_success" -le "$now" ] 2>/dev/null; then
|
||||
base="$last_success"
|
||||
fi
|
||||
|
||||
# A failed or currently-running refresh must also move the retry window
|
||||
# forward so refreshd cannot enter an immediate retry loop. A successful
|
||||
# refresh is naturally newer than its attempt timestamp and wins here.
|
||||
if [ "$last_attempt" -gt "$base" ] 2>/dev/null && [ "$last_attempt" -le "$now" ] 2>/dev/null; then
|
||||
base="$last_attempt"
|
||||
fi
|
||||
|
||||
printf '%s\n' "$base"
|
||||
}
|
||||
|
||||
has_recent_success() {
|
||||
local interval="$1"
|
||||
local now last_success age
|
||||
|
||||
now="$(date +%s)"
|
||||
last_success="$(status_epoch last_success)"
|
||||
|
||||
[ "$last_success" -gt 0 ] 2>/dev/null || return 1
|
||||
[ "$last_success" -le "$now" ] 2>/dev/null || return 1
|
||||
|
||||
age=$((now - last_success))
|
||||
[ "$age" -lt "$interval" ] 2>/dev/null
|
||||
}
|
||||
|
||||
set_next_refresh_epoch() {
|
||||
local epoch="$1"
|
||||
|
||||
is_valid_integer "$epoch" || epoch=0
|
||||
ss_status_set next_refresh_at "$epoch"
|
||||
}
|
||||
|
||||
clear_next_refresh_at() {
|
||||
ss_status_set next_refresh_at "0"
|
||||
}
|
||||
|
||||
wait_until_refresh_due() {
|
||||
local interval="$1"
|
||||
local fallback_epoch="$2"
|
||||
local now base due remaining
|
||||
|
||||
while :; do
|
||||
[ "$ss_should_terminate" -eq 1 ] && return 1
|
||||
|
||||
now="$(date +%s)"
|
||||
base="$(schedule_base_epoch "$now")"
|
||||
if [ "$base" -le 0 ] 2>/dev/null; then
|
||||
base="$fallback_epoch"
|
||||
fi
|
||||
|
||||
due=$((base + interval))
|
||||
remaining=$((due - now))
|
||||
|
||||
if [ "$remaining" -le 0 ] 2>/dev/null; then
|
||||
clear_next_refresh_at
|
||||
return 0
|
||||
fi
|
||||
|
||||
set_next_refresh_epoch "$due"
|
||||
sleep_seconds "$remaining" || return 1
|
||||
|
||||
# A manual refresh may have completed while refreshd was sleeping.
|
||||
# Recalculate from the latest last_success/last_attempt before running.
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
local interval
|
||||
local boot_delay
|
||||
local refresh_on_boot
|
||||
local schedule_anchor
|
||||
|
||||
refreshd_lock || exit 0
|
||||
|
||||
ss_load_config || exit 1
|
||||
if [ ! -s "${RUNNING_STATUS_FILE}" ]; then
|
||||
ss_status_reset || true
|
||||
fi
|
||||
ss_sync_blocklist_status
|
||||
ss_status_set status "idle"
|
||||
ss_status_set stage "starting"
|
||||
|
||||
interval="$(ss_config_get config refresh_interval_s 28800)"
|
||||
boot_delay="$(ss_config_get config boot_start_delay_s 30)"
|
||||
refresh_on_boot="$(ss_config_get config refresh_on_boot 1)"
|
||||
|
||||
is_valid_integer "$interval" || interval=28800
|
||||
[ "$interval" -gt 0 ] 2>/dev/null || interval=28800
|
||||
|
||||
is_valid_integer "$boot_delay" || boot_delay=30
|
||||
|
||||
wait_for_wan_if_needed
|
||||
ss_status_set stage "boot_delay"
|
||||
set_next_refresh_epoch "$(($(date +%s) + boot_delay))"
|
||||
sleep_seconds "$boot_delay" || exit 0
|
||||
|
||||
if [ "$refresh_on_boot" = "1" ] && [ "${ss_enabled}" = "1" ]; then
|
||||
if has_recent_success "$interval"; then
|
||||
log_info "Skipping boot refresh because a recent refresh already succeeded"
|
||||
ss_status_set status "idle"
|
||||
ss_status_set stage "boot_refresh_skipped"
|
||||
else
|
||||
ss_status_set status "running"
|
||||
ss_status_set stage "boot_refresh"
|
||||
clear_next_refresh_at
|
||||
safeshield_force_download
|
||||
fi
|
||||
fi
|
||||
|
||||
schedule_anchor="$(date +%s)"
|
||||
|
||||
while :; do
|
||||
ss_load_config || {
|
||||
schedule_anchor="$(date +%s)"
|
||||
sleep_seconds 5 || break
|
||||
continue
|
||||
}
|
||||
|
||||
interval="$(ss_config_get config refresh_interval_s 28800)"
|
||||
is_valid_integer "$interval" || interval=28800
|
||||
[ "$interval" -gt 0 ] 2>/dev/null || interval=28800
|
||||
|
||||
if [ "${ss_enabled}" != "1" ]; then
|
||||
ss_status_set status "disabled"
|
||||
ss_status_set stage "disabled"
|
||||
clear_next_refresh_at
|
||||
sleep_seconds "$interval" || break
|
||||
schedule_anchor="$(date +%s)"
|
||||
continue
|
||||
fi
|
||||
|
||||
ss_status_set status "idle"
|
||||
ss_status_set stage "scheduled_wait"
|
||||
wait_until_refresh_due "$interval" "$schedule_anchor" || break
|
||||
|
||||
ss_load_config || {
|
||||
schedule_anchor="$(date +%s)"
|
||||
continue
|
||||
}
|
||||
|
||||
if [ "${ss_enabled}" != "1" ]; then
|
||||
ss_status_set status "disabled"
|
||||
ss_status_set stage "disabled"
|
||||
clear_next_refresh_at
|
||||
schedule_anchor="$(date +%s)"
|
||||
continue
|
||||
fi
|
||||
|
||||
wait_for_wan_if_needed || true
|
||||
|
||||
ss_status_set status "running"
|
||||
ss_status_set stage "scheduled_refresh"
|
||||
clear_next_refresh_at
|
||||
safeshield_force_download
|
||||
schedule_anchor="$(date +%s)"
|
||||
done
|
||||
|
||||
ss_load_config >/dev/null 2>&1 || true
|
||||
if [ "${ss_enabled:-0}" = "1" ]; then
|
||||
ss_status_set status "idle"
|
||||
ss_status_set stage "stopped"
|
||||
else
|
||||
ss_status_set status "disabled"
|
||||
ss_status_set stage "disabled"
|
||||
fi
|
||||
clear_next_refresh_at
|
||||
}
|
||||
|
||||
trap "handle_term" TERM INT
|
||||
trap "refreshd_unlock; clear_next_refresh_at" EXIT
|
||||
|
||||
main "$@"
|
||||
+496
@@ -0,0 +1,496 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=ash
|
||||
|
||||
# Variables are populated by sourced SafeShield helpers and ss_load_config().
|
||||
# shellcheck disable=SC2154
|
||||
LC_ALL=C
|
||||
export LC_ALL
|
||||
umask 077
|
||||
|
||||
SS_STATS_UPLOAD_FUNCTIONS_LIB="${SS_STATS_UPLOAD_FUNCTIONS_LIB:-/lib/functions.sh}"
|
||||
SS_STATS_UPLOAD_CORE_LIB="${SS_STATS_UPLOAD_CORE_LIB:-/usr/lib/safeshield/core.sh}"
|
||||
SS_STATS_UPLOAD_INTERVAL_S="${SS_STATS_UPLOAD_INTERVAL_S:-1800}"
|
||||
SS_STATS_UPLOAD_IDLE_WAIT_S="${SS_STATS_UPLOAD_IDLE_WAIT_S:-15}"
|
||||
SS_STATS_UPLOAD_RETRY_DELAY_S="${SS_STATS_UPLOAD_RETRY_DELAY_S:-2}"
|
||||
SS_STATS_UPLOAD_FULL_EVERY="${SS_STATS_UPLOAD_FULL_EVERY:-23}"
|
||||
SS_STATS_UPLOAD_DENIED_RECHECK_S="${SS_STATS_UPLOAD_DENIED_RECHECK_S:-43200}"
|
||||
SS_STATS_UPLOAD_RESPONSE_FILE="${SS_STATS_UPLOAD_RESPONSE_FILE:-/tmp/safeshield/statistics/upload.response.json}"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
. "$SS_STATS_UPLOAD_FUNCTIONS_LIB"
|
||||
# shellcheck disable=SC1090
|
||||
. "$SS_STATS_UPLOAD_CORE_LIB"
|
||||
|
||||
ss_should_terminate=0
|
||||
upload_sleep_pid=''
|
||||
upload_state_generation=''
|
||||
upload_state_last_success_seq=0
|
||||
upload_state_last_full_seq=0
|
||||
upload_state_recent_successes=0
|
||||
upload_state_needs_full=1
|
||||
upload_pending_mode=''
|
||||
upload_pending_generation=''
|
||||
upload_pending_seq=0
|
||||
|
||||
upload_handle_term() {
|
||||
ss_should_terminate=1
|
||||
if [ -n "$upload_sleep_pid" ]; then
|
||||
kill "$upload_sleep_pid" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
upload_sleep() {
|
||||
local seconds="$1"
|
||||
local rc=0
|
||||
|
||||
[ "$ss_should_terminate" -eq 1 ] && return 1
|
||||
[ "$seconds" -gt 0 ] 2>/dev/null || return 0
|
||||
|
||||
sleep "$seconds" &
|
||||
upload_sleep_pid=$!
|
||||
wait "$upload_sleep_pid" 2>/dev/null || rc=$?
|
||||
upload_sleep_pid=''
|
||||
|
||||
[ "$ss_should_terminate" -eq 1 ] && return 1
|
||||
[ "$rc" -eq 0 ]
|
||||
}
|
||||
|
||||
upload_state_reset() {
|
||||
upload_state_generation=''
|
||||
upload_state_last_success_seq=0
|
||||
upload_state_last_full_seq=0
|
||||
upload_state_recent_successes=0
|
||||
upload_state_needs_full=1
|
||||
}
|
||||
|
||||
upload_state_load() {
|
||||
local old_ifs
|
||||
|
||||
upload_state_reset
|
||||
[ -s "$SS_STATISTICS_UPLOAD_STATE_FILE" ] || return 0
|
||||
|
||||
old_ifs="$IFS"
|
||||
IFS="$(printf '\t')"
|
||||
read -r upload_state_generation upload_state_last_success_seq upload_state_last_full_seq upload_state_recent_successes upload_state_needs_full <"$SS_STATISTICS_UPLOAD_STATE_FILE" || true
|
||||
IFS="$old_ifs"
|
||||
|
||||
is_valid_integer "$upload_state_last_success_seq" || upload_state_last_success_seq=0
|
||||
is_valid_integer "$upload_state_last_full_seq" || upload_state_last_full_seq=0
|
||||
is_valid_integer "$upload_state_recent_successes" || upload_state_recent_successes=0
|
||||
case "$upload_state_needs_full" in
|
||||
0 | 1) ;;
|
||||
*) upload_state_needs_full=1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
upload_state_save() {
|
||||
local tmp="${SS_STATISTICS_UPLOAD_STATE_FILE}.tmp.$$"
|
||||
|
||||
mkdir -p "${SS_STATISTICS_UPLOAD_STATE_FILE%/*}" || return 1
|
||||
printf '%s\t%s\t%s\t%s\t%s\n' \
|
||||
"$upload_state_generation" \
|
||||
"$upload_state_last_success_seq" \
|
||||
"$upload_state_last_full_seq" \
|
||||
"$upload_state_recent_successes" \
|
||||
"$upload_state_needs_full" >"$tmp" || {
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
}
|
||||
mv -f "$tmp" "$SS_STATISTICS_UPLOAD_STATE_FILE" || {
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
upload_mark_needs_full() {
|
||||
local generation="${1:-$upload_state_generation}"
|
||||
|
||||
if [ -n "$generation" ] && [ "$upload_state_generation" != "$generation" ]; then
|
||||
upload_state_generation="$generation"
|
||||
upload_state_last_success_seq=0
|
||||
upload_state_last_full_seq=0
|
||||
upload_state_recent_successes=0
|
||||
fi
|
||||
upload_state_needs_full=1
|
||||
upload_state_save >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
upload_pending_clear() {
|
||||
rm -f "$SS_STATISTICS_UPLOAD_PENDING_FILE" "$SS_STATISTICS_UPLOAD_PENDING_META_FILE"
|
||||
upload_pending_mode=''
|
||||
upload_pending_generation=''
|
||||
upload_pending_seq=0
|
||||
}
|
||||
|
||||
upload_pending_load() {
|
||||
local old_ifs payload_generation payload_seq
|
||||
|
||||
upload_pending_mode=''
|
||||
upload_pending_generation=''
|
||||
upload_pending_seq=0
|
||||
[ -s "$SS_STATISTICS_UPLOAD_PENDING_FILE" ] || return 1
|
||||
[ -s "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" ] || {
|
||||
upload_pending_clear
|
||||
return 1
|
||||
}
|
||||
|
||||
old_ifs="$IFS"
|
||||
IFS="$(printf '\t')"
|
||||
read -r upload_pending_mode upload_pending_generation upload_pending_seq <"$SS_STATISTICS_UPLOAD_PENDING_META_FILE" || true
|
||||
IFS="$old_ifs"
|
||||
|
||||
case "$upload_pending_mode" in
|
||||
full | recent) ;;
|
||||
*)
|
||||
upload_pending_clear
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
is_valid_integer "$upload_pending_seq" || {
|
||||
upload_pending_clear
|
||||
return 1
|
||||
}
|
||||
[ "$upload_pending_seq" -gt 0 ] 2>/dev/null || {
|
||||
upload_pending_clear
|
||||
return 1
|
||||
}
|
||||
|
||||
payload_generation="$(ss_json_get_file "$SS_STATISTICS_UPLOAD_PENDING_FILE" '@.generation_id')"
|
||||
payload_seq="$(ss_json_get_file "$SS_STATISTICS_UPLOAD_PENDING_FILE" '@.snapshot_seq')"
|
||||
if [ "$payload_generation" != "$upload_pending_generation" ] || [ "$payload_seq" != "$upload_pending_seq" ]; then
|
||||
upload_pending_clear
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
upload_source_is_v3() {
|
||||
local file="$1"
|
||||
local schema_name schema_version seq generation
|
||||
|
||||
[ -s "$file" ] || return 1
|
||||
schema_name="$(ss_json_get_file "$file" '@.schema.name')"
|
||||
schema_version="$(ss_json_get_file "$file" '@.schema.version')"
|
||||
generation="$(ss_json_get_file "$file" '@.generation_id')"
|
||||
seq="$(ss_json_get_file "$file" '@.snapshot_seq')"
|
||||
[ "$schema_name" = 'safeshield.statistics' ] || return 1
|
||||
[ "$schema_version" = '3' ] || return 1
|
||||
[ -n "$generation" ] || return 1
|
||||
is_valid_integer "$seq" || return 1
|
||||
[ "$seq" -gt 0 ] 2>/dev/null
|
||||
}
|
||||
|
||||
upload_pending_create() {
|
||||
local source mode source_generation source_seq full_generation full_seq
|
||||
local tmp_payload tmp_meta
|
||||
|
||||
upload_state_load
|
||||
upload_source_is_v3 "$SS_STATISTICS_JSON_FILE" || return 1
|
||||
full_generation="$(ss_json_get_file "$SS_STATISTICS_JSON_FILE" '@.generation_id')"
|
||||
full_seq="$(ss_json_get_file "$SS_STATISTICS_JSON_FILE" '@.snapshot_seq')"
|
||||
|
||||
if [ "$upload_state_generation" != "$full_generation" ]; then
|
||||
upload_state_generation="$full_generation"
|
||||
upload_state_last_success_seq=0
|
||||
upload_state_last_full_seq=0
|
||||
upload_state_recent_successes=0
|
||||
upload_state_needs_full=1
|
||||
upload_state_save >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
mode='recent'
|
||||
source="$SS_STATISTICS_UPLOAD_JSON_FILE"
|
||||
if [ "$upload_state_needs_full" = '1' ] ||
|
||||
[ "$upload_state_last_full_seq" -le 0 ] 2>/dev/null ||
|
||||
[ "$upload_state_recent_successes" -ge "$SS_STATS_UPLOAD_FULL_EVERY" ] 2>/dev/null; then
|
||||
mode='full'
|
||||
source="$SS_STATISTICS_JSON_FILE"
|
||||
elif ! upload_source_is_v3 "$source"; then
|
||||
mode='full'
|
||||
source="$SS_STATISTICS_JSON_FILE"
|
||||
fi
|
||||
|
||||
tmp_payload="${SS_STATISTICS_UPLOAD_PENDING_FILE}.tmp.$$"
|
||||
tmp_meta="${SS_STATISTICS_UPLOAD_PENDING_META_FILE}.tmp.$$"
|
||||
mkdir -p "${SS_STATISTICS_UPLOAD_PENDING_FILE%/*}" || return 1
|
||||
cp "$source" "$tmp_payload" || {
|
||||
rm -f "$tmp_payload"
|
||||
return 1
|
||||
}
|
||||
|
||||
upload_source_is_v3 "$tmp_payload" || {
|
||||
rm -f "$tmp_payload"
|
||||
return 1
|
||||
}
|
||||
source_generation="$(ss_json_get_file "$tmp_payload" '@.generation_id')"
|
||||
source_seq="$(ss_json_get_file "$tmp_payload" '@.snapshot_seq')"
|
||||
if [ "$source_generation" != "$full_generation" ]; then
|
||||
rm -f "$tmp_payload"
|
||||
return 1
|
||||
fi
|
||||
if [ "$mode" = 'recent' ] && [ "$source_seq" != "$full_seq" ]; then
|
||||
# The collector publishes the full snapshot before its recent projection.
|
||||
# Avoid promoting a previous projection while those two atomic writes cross.
|
||||
rm -f "$tmp_payload"
|
||||
return 1
|
||||
fi
|
||||
if [ "$source_seq" -le "$upload_state_last_success_seq" ] 2>/dev/null; then
|
||||
rm -f "$tmp_payload"
|
||||
return 2
|
||||
fi
|
||||
|
||||
printf '%s\t%s\t%s\n' "$mode" "$source_generation" "$source_seq" >"$tmp_meta" || {
|
||||
rm -f "$tmp_payload" "$tmp_meta"
|
||||
return 1
|
||||
}
|
||||
mv -f "$tmp_payload" "$SS_STATISTICS_UPLOAD_PENDING_FILE" || {
|
||||
rm -f "$tmp_payload" "$tmp_meta"
|
||||
return 1
|
||||
}
|
||||
mv -f "$tmp_meta" "$SS_STATISTICS_UPLOAD_PENDING_META_FILE" || {
|
||||
rm -f "$tmp_meta"
|
||||
upload_pending_clear
|
||||
return 1
|
||||
}
|
||||
|
||||
upload_pending_mode="$mode"
|
||||
upload_pending_generation="$source_generation"
|
||||
upload_pending_seq="$source_seq"
|
||||
return 0
|
||||
}
|
||||
|
||||
upload_ensure_credentials() {
|
||||
ss_statistics_upload_denied && return 2
|
||||
ss_statistics_load_upload_credentials && return 0
|
||||
log_info 'Refreshing Hub statistics upload credentials'
|
||||
ss_statistics_refresh_upload_credentials || return $?
|
||||
ss_statistics_load_upload_credentials
|
||||
}
|
||||
|
||||
upload_response_valid() {
|
||||
local response="$1"
|
||||
local status generation server_seq
|
||||
|
||||
[ -s "$response" ] || return 1
|
||||
status="$(ss_json_get_file "$response" '@.status')"
|
||||
generation="$(ss_json_get_file "$response" '@.generation_id')"
|
||||
server_seq="$(ss_json_get_file "$response" '@.last_snapshot_seq')"
|
||||
case "$status" in
|
||||
applied | duplicate | stale) ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
[ "$generation" = "$upload_pending_generation" ] || return 1
|
||||
is_valid_integer "$server_seq" || return 1
|
||||
[ "$server_seq" -ge "$upload_pending_seq" ] 2>/dev/null || return 1
|
||||
|
||||
SS_STATS_UPLOAD_RESPONSE_STATUS="$status"
|
||||
SS_STATS_UPLOAD_RESPONSE_SEQ="$server_seq"
|
||||
return 0
|
||||
}
|
||||
|
||||
upload_record_success() {
|
||||
local result_status="$1"
|
||||
local server_seq="$2"
|
||||
|
||||
upload_state_load
|
||||
if [ "$upload_state_generation" != "$upload_pending_generation" ]; then
|
||||
upload_state_generation="$upload_pending_generation"
|
||||
upload_state_last_success_seq=0
|
||||
upload_state_last_full_seq=0
|
||||
upload_state_recent_successes=0
|
||||
upload_state_needs_full=1
|
||||
fi
|
||||
if [ "$server_seq" -gt "$upload_state_last_success_seq" ] 2>/dev/null; then
|
||||
upload_state_last_success_seq="$server_seq"
|
||||
fi
|
||||
|
||||
if [ "$upload_pending_mode" = 'full' ] && [ "$result_status" != 'stale' ]; then
|
||||
upload_state_last_full_seq="$upload_pending_seq"
|
||||
upload_state_recent_successes=0
|
||||
upload_state_needs_full=0
|
||||
else
|
||||
if [ "$result_status" = 'stale' ]; then
|
||||
upload_state_needs_full=1
|
||||
elif [ "$upload_state_needs_full" = '0' ]; then
|
||||
upload_state_recent_successes=$((upload_state_recent_successes + 1))
|
||||
fi
|
||||
fi
|
||||
upload_state_save >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
upload_record_failure() {
|
||||
upload_mark_needs_full "$upload_pending_generation"
|
||||
}
|
||||
|
||||
upload_send_pending() {
|
||||
local retries reauthenticated=0 response_status response_seq credentials_rc=0 refresh_rc=0
|
||||
|
||||
upload_pending_load || return 1
|
||||
upload_ensure_credentials || credentials_rc=$?
|
||||
case "$credentials_rc" in
|
||||
0) ;;
|
||||
2)
|
||||
upload_pending_clear
|
||||
return 2
|
||||
;;
|
||||
*)
|
||||
upload_record_failure 'statistics_credentials_unavailable'
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
retries=1
|
||||
while [ "$retries" -le "${ss_download_retry:-3}" ]; do
|
||||
[ "$ss_should_terminate" -eq 1 ] && return 130
|
||||
rm -f "$SS_STATS_UPLOAD_RESPONSE_FILE"
|
||||
if ss_http_post_json \
|
||||
"$SS_STATISTICS_UPLOAD_URL" \
|
||||
"$SS_STATISTICS_UPLOAD_PENDING_FILE" \
|
||||
"$SS_STATS_UPLOAD_RESPONSE_FILE" \
|
||||
"Bearer $SS_STATISTICS_UPLOAD_TOKEN"; then
|
||||
if upload_response_valid "$SS_STATS_UPLOAD_RESPONSE_FILE"; then
|
||||
response_status="$SS_STATS_UPLOAD_RESPONSE_STATUS"
|
||||
response_seq="$SS_STATS_UPLOAD_RESPONSE_SEQ"
|
||||
upload_record_success "$response_status" "$response_seq"
|
||||
log_info "Uploaded SafeShield statistics (${upload_pending_mode}, seq=${upload_pending_seq}, status=${response_status})"
|
||||
upload_pending_clear
|
||||
rm -f "$SS_STATS_UPLOAD_RESPONSE_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
upload_record_failure 'statistics_upload_invalid_response' '200'
|
||||
log_warn 'Hub statistics upload returned an invalid success response; preserving pending payload for retry'
|
||||
rm -f "$SS_STATS_UPLOAD_RESPONSE_FILE"
|
||||
return 1
|
||||
fi
|
||||
|
||||
case "${SS_HTTP_STATUS:-}" in
|
||||
401)
|
||||
if [ "$reauthenticated" -eq 0 ]; then
|
||||
refresh_rc=0
|
||||
reauthenticated=1
|
||||
ss_statistics_clear_upload_credentials
|
||||
ss_statistics_refresh_upload_credentials || refresh_rc=$?
|
||||
if [ "$refresh_rc" -eq 0 ] && ss_statistics_load_upload_credentials; then
|
||||
continue
|
||||
fi
|
||||
if [ "$refresh_rc" -eq 2 ]; then
|
||||
log_info 'Cloud statistics upload is no longer enabled for the current license'
|
||||
upload_pending_clear
|
||||
return 2
|
||||
fi
|
||||
fi
|
||||
upload_record_failure 'statistics_upload_auth_failed' '401'
|
||||
log_warn 'Hub rejected statistics upload credentials'
|
||||
return 1
|
||||
;;
|
||||
400 | 409 | 413)
|
||||
upload_record_failure "statistics_upload_http_${SS_HTTP_STATUS}" "$SS_HTTP_STATUS"
|
||||
log_error "Hub rejected statistics payload (HTTP ${SS_HTTP_STATUS}); discarding this snapshot and scheduling full reconciliation"
|
||||
upload_pending_clear
|
||||
rm -f "$SS_STATS_UPLOAD_RESPONSE_FILE"
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
|
||||
upload_record_failure 'statistics_upload_failed' "${SS_HTTP_STATUS:-0}"
|
||||
retries=$((retries + 1))
|
||||
if [ "$retries" -le "${ss_download_retry:-3}" ]; then
|
||||
upload_sleep "$SS_STATS_UPLOAD_RETRY_DELAY_S" || return 130
|
||||
fi
|
||||
done
|
||||
|
||||
log_warn "Statistics upload failed after ${ss_download_retry:-3} attempt(s); pending payload will be retried"
|
||||
return 1
|
||||
}
|
||||
|
||||
main() {
|
||||
local create_rc=0 wait_s entitlement_rc
|
||||
|
||||
ss_load_config || return 1
|
||||
[ "${ss_enabled}" = '1' ] || return 0
|
||||
[ "${ss_statistics_enabled}" = '1' ] || return 0
|
||||
mkdir -p "$SS_STATISTICS_DIR" || return 1
|
||||
|
||||
is_valid_integer "$SS_STATS_UPLOAD_INTERVAL_S" || SS_STATS_UPLOAD_INTERVAL_S=1800
|
||||
[ "$SS_STATS_UPLOAD_INTERVAL_S" -ge 60 ] 2>/dev/null || SS_STATS_UPLOAD_INTERVAL_S=1800
|
||||
is_valid_integer "$SS_STATS_UPLOAD_IDLE_WAIT_S" || SS_STATS_UPLOAD_IDLE_WAIT_S=15
|
||||
[ "$SS_STATS_UPLOAD_IDLE_WAIT_S" -ge 1 ] 2>/dev/null || SS_STATS_UPLOAD_IDLE_WAIT_S=15
|
||||
is_valid_integer "$SS_STATS_UPLOAD_RETRY_DELAY_S" || SS_STATS_UPLOAD_RETRY_DELAY_S=2
|
||||
is_valid_integer "$SS_STATS_UPLOAD_FULL_EVERY" || SS_STATS_UPLOAD_FULL_EVERY=23
|
||||
[ "$SS_STATS_UPLOAD_FULL_EVERY" -ge 1 ] 2>/dev/null || SS_STATS_UPLOAD_FULL_EVERY=23
|
||||
is_valid_integer "$SS_STATS_UPLOAD_DENIED_RECHECK_S" || SS_STATS_UPLOAD_DENIED_RECHECK_S=43200
|
||||
[ "$SS_STATS_UPLOAD_DENIED_RECHECK_S" -ge 60 ] 2>/dev/null || SS_STATS_UPLOAD_DENIED_RECHECK_S=43200
|
||||
|
||||
upload_state_load
|
||||
while [ "$ss_should_terminate" -eq 0 ]; do
|
||||
ss_load_config || {
|
||||
upload_sleep "$SS_STATS_UPLOAD_IDLE_WAIT_S" || break
|
||||
continue
|
||||
}
|
||||
if [ "${ss_enabled}" != '1' ] || [ "${ss_statistics_enabled}" != '1' ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
wait_s="$SS_STATS_UPLOAD_INTERVAL_S"
|
||||
if [ -z "${ss_license_key:-}" ]; then
|
||||
ss_statistics_upload_denied || ss_statistics_disable_cloud_upload >/dev/null 2>&1 || true
|
||||
fi
|
||||
if ss_statistics_upload_denied; then
|
||||
upload_pending_clear
|
||||
if [ -n "${ss_license_key:-}" ] && ss_statistics_upload_denied_recheck_due "$SS_STATS_UPLOAD_DENIED_RECHECK_S"; then
|
||||
log_info 'Rechecking Hub statistics upload entitlement'
|
||||
# Stamp this attempt before network I/O so a failed resolve cannot
|
||||
# turn into a routine upload retry loop while entitlement is denied.
|
||||
ss_statistics_set_upload_entitlement denied >/dev/null 2>&1 || true
|
||||
entitlement_rc=0
|
||||
ss_statistics_refresh_upload_credentials || entitlement_rc=$?
|
||||
case "$entitlement_rc" in
|
||||
0) ;;
|
||||
2)
|
||||
upload_sleep "$wait_s" || break
|
||||
continue
|
||||
;;
|
||||
*)
|
||||
log_warn 'Hub statistics entitlement recheck failed; keeping cloud upload disabled'
|
||||
upload_sleep "$wait_s" || break
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
if ss_statistics_upload_denied; then
|
||||
upload_sleep "$wait_s" || break
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if upload_pending_load; then
|
||||
upload_send_pending || true
|
||||
else
|
||||
create_rc=0
|
||||
upload_pending_create || create_rc=$?
|
||||
case "$create_rc" in
|
||||
0)
|
||||
upload_send_pending || true
|
||||
;;
|
||||
1)
|
||||
wait_s="$SS_STATS_UPLOAD_IDLE_WAIT_S"
|
||||
;;
|
||||
2)
|
||||
# The collector has not produced a sequence newer than the last ACK yet.
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
upload_sleep "$wait_s" || break
|
||||
done
|
||||
|
||||
rm -f "$SS_STATS_UPLOAD_RESPONSE_FILE"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ "${SS_STATS_UPLOAD_LIBRARY_ONLY:-0}" != '1' ]; then
|
||||
trap 'upload_handle_term' TERM INT
|
||||
trap 'rm -f "$SS_STATS_UPLOAD_RESPONSE_FILE"' EXIT
|
||||
main "$@"
|
||||
fi
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/ucode
|
||||
'use strict';
|
||||
|
||||
const ubus = require('ubus');
|
||||
|
||||
function clean(value) {
|
||||
return replace(sprintf('%s', value || ''), /[\t\r\n]/g, '_');
|
||||
}
|
||||
|
||||
function main() {
|
||||
let conn = ubus.connect();
|
||||
if (!conn) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data = conn.call('dhcp', 'ipv6leases', {});
|
||||
if (type(data) != 'object' || type(data.device) != 'object') {
|
||||
conn.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
for (let _, device in data.device) {
|
||||
if (type(device) != 'object' || type(device.leases) != 'array') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let lease in device.leases) {
|
||||
if (type(lease) != 'object') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let duid = lc(clean(lease.duid));
|
||||
let hostname = clean(lease.hostname);
|
||||
let addresses = lease['ipv6-addr'];
|
||||
if (!duid || type(addresses) != 'array') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let address in addresses) {
|
||||
if (type(address) != 'object') {
|
||||
continue;
|
||||
}
|
||||
let ip = clean(address.address);
|
||||
if (ip) {
|
||||
printf('%s\t%s\t%s\n', ip, duid, hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn.disconnect();
|
||||
}
|
||||
|
||||
main();
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/ucode
|
||||
'use strict';
|
||||
|
||||
let ubus = require('ubus').connect();
|
||||
|
||||
function token(value) {
|
||||
let text = sprintf('%s', value == null ? '' : value);
|
||||
return replace(text, /[\t\r\n]/g, '');
|
||||
}
|
||||
|
||||
function uint_or_null(value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let number = int(value);
|
||||
return (type(number) == 'int' && number >= 0) ? number : null;
|
||||
}
|
||||
|
||||
function optional_uint(value) {
|
||||
let number = uint_or_null(value);
|
||||
return number == null ? 0 : number;
|
||||
}
|
||||
|
||||
if (!ubus) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let data = ubus.call('dnsmasq', 'safeshield_stats', {});
|
||||
if (type(data) != 'object' || uint_or_null(data.schema) != 1) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let instance_id = token(data.instance_id);
|
||||
if (!instance_id || type(data.totals) != 'object' || type(data.clients) != 'array') {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let total_queries = uint_or_null(data.totals.queries);
|
||||
let total_blocked = uint_or_null(data.totals.blocked);
|
||||
if (total_queries == null || total_blocked == null) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let transport_scope = token(data.transport_scope || 'unknown');
|
||||
let client_capacity = optional_uint(data.client_capacity);
|
||||
let tracked_clients = optional_uint(data.tracked_clients);
|
||||
let untracked_queries = optional_uint(data.untracked_queries);
|
||||
let untracked_blocked = optional_uint(data.untracked_blocked);
|
||||
let normalized_clients = [];
|
||||
|
||||
for (let client in data.clients) {
|
||||
if (type(client) != 'object') {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let address = token(client.address);
|
||||
let queries = uint_or_null(client.queries);
|
||||
let blocked = uint_or_null(client.blocked);
|
||||
if (!address || queries == null || blocked == null) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
push(normalized_clients, {
|
||||
address: address,
|
||||
queries: queries,
|
||||
blocked: blocked
|
||||
});
|
||||
}
|
||||
|
||||
printf('snapshot\t%s\t%s\t%d\t%d\t%d\t%d\t%d\t%d\n',
|
||||
instance_id,
|
||||
transport_scope,
|
||||
client_capacity,
|
||||
tracked_clients,
|
||||
untracked_queries,
|
||||
untracked_blocked,
|
||||
total_queries,
|
||||
total_blocked);
|
||||
|
||||
for (let client in normalized_clients) {
|
||||
printf('client\t%s\t%d\t%d\n',
|
||||
client.address,
|
||||
client.queries,
|
||||
client.blocked);
|
||||
}
|
||||
|
||||
print('commit\n');
|
||||
exit(0);
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
#!/bin/sh
|
||||
# shellcheck shell=ash
|
||||
|
||||
# Variables are populated by sourced SafeShield helpers and ss_load_config().
|
||||
# shellcheck disable=SC2154
|
||||
LC_ALL=C
|
||||
export LC_ALL
|
||||
umask 077
|
||||
|
||||
SS_STATSD_FUNCTIONS_LIB="${SS_STATSD_FUNCTIONS_LIB:-/lib/functions.sh}"
|
||||
SS_STATSD_CORE_LIB="${SS_STATSD_CORE_LIB:-/usr/lib/safeshield/core.sh}"
|
||||
SS_STATSD_AWK_DIR="${SS_STATSD_AWK_DIR:-/usr/lib/safeshield/statistics}"
|
||||
SS_STATSD_POLL_COMMAND="${SS_STATSD_POLL_COMMAND:-/usr/libexec/safeshield-stats-poll}"
|
||||
SS_STATSD_IDENTITY_COMMAND="${SS_STATSD_IDENTITY_COMMAND:-/usr/libexec/safeshield-stats-identities}"
|
||||
SS_STATSD_PERSISTENT_STATE_FILE="${SS_STATSD_PERSISTENT_STATE_FILE:-/etc/safeshield/statistics-state.tsv}"
|
||||
SS_STATSD_PERSISTENT_JOURNAL_FILE="${SS_STATSD_PERSISTENT_JOURNAL_FILE:-/etc/safeshield/statistics-journal.tsv}"
|
||||
SS_STATSD_SEQUENCE_FILE="${SS_STATSD_SEQUENCE_FILE:-/etc/safeshield/statistics-sequence.tsv}"
|
||||
SS_STATSD_SEQUENCE_RESERVATION_SIZE="${SS_STATSD_SEQUENCE_RESERVATION_SIZE:-4096}"
|
||||
SS_STATSD_UPLOAD_WINDOW_HOURS="${SS_STATSD_UPLOAD_WINDOW_HOURS:-2}"
|
||||
SS_STATSD_PERSISTENT_INTERVAL_S="${SS_STATSD_PERSISTENT_INTERVAL_S:-3600}"
|
||||
SS_STATSD_PERSISTENT_COMPACT_INTERVAL_S="${SS_STATSD_PERSISTENT_COMPACT_INTERVAL_S:-604800}"
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
. "$SS_STATSD_FUNCTIONS_LIB"
|
||||
# shellcheck disable=SC1090
|
||||
. "$SS_STATSD_CORE_LIB"
|
||||
|
||||
stats_poller_pid=''
|
||||
stats_awk_pid=''
|
||||
stats_fifo=''
|
||||
stats_lock_dir=''
|
||||
stats_lock_owned=0
|
||||
|
||||
stats_new_generation_id() {
|
||||
local generation_id="${SS_STATSD_GENERATION_ID:-}"
|
||||
|
||||
if [ -n "$generation_id" ]; then
|
||||
printf '%s\n' "$generation_id"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -r /proc/sys/kernel/random/uuid ]; then
|
||||
IFS= read -r generation_id </proc/sys/kernel/random/uuid || generation_id=''
|
||||
fi
|
||||
|
||||
if [ -z "$generation_id" ]; then
|
||||
generation_id="fallback-$(date +%s 2>/dev/null || printf '0')-$$"
|
||||
fi
|
||||
|
||||
printf '%s\n' "$generation_id"
|
||||
}
|
||||
|
||||
stats_process_running() {
|
||||
local pid="${1:-}"
|
||||
[ -n "$pid" ] || return 1
|
||||
kill -0 "$pid" 2>/dev/null
|
||||
}
|
||||
|
||||
stats_cleanup() {
|
||||
trap - EXIT HUP INT TERM
|
||||
|
||||
# Stop the low-frequency UBus poller first. awk then receives EOF and runs
|
||||
# its END block, which performs the final snapshot and persistence flush.
|
||||
if stats_process_running "$stats_poller_pid"; then
|
||||
kill "$stats_poller_pid" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
[ -n "$stats_poller_pid" ] && wait "$stats_poller_pid" 2>/dev/null || true
|
||||
stats_poller_pid=''
|
||||
|
||||
[ -n "$stats_awk_pid" ] && wait "$stats_awk_pid" 2>/dev/null || true
|
||||
stats_awk_pid=''
|
||||
|
||||
[ -n "$stats_fifo" ] && rm -f "$stats_fifo"
|
||||
|
||||
if [ "$stats_lock_owned" -eq 1 ] && [ -n "$stats_lock_dir" ]; then
|
||||
rm -rf "$stats_lock_dir"
|
||||
fi
|
||||
}
|
||||
|
||||
stats_terminate() {
|
||||
local rc="${1:-0}"
|
||||
stats_cleanup
|
||||
exit "$rc"
|
||||
}
|
||||
|
||||
stats_acquire_lock() {
|
||||
local owner_pid=''
|
||||
|
||||
stats_lock_dir="${SS_STATISTICS_DIR}/collector.lock"
|
||||
|
||||
if mkdir "$stats_lock_dir" 2>/dev/null; then
|
||||
stats_lock_owned=1
|
||||
printf '%s\n' "$$" >"${stats_lock_dir}/pid"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -r "${stats_lock_dir}/pid" ]; then
|
||||
owner_pid="$(cat "${stats_lock_dir}/pid" 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if stats_process_running "$owner_pid"; then
|
||||
log_warn "Statistics collector is already running (pid=${owner_pid})"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_warn 'Removing stale statistics collector lock'
|
||||
rm -rf "$stats_lock_dir" || return 1
|
||||
mkdir "$stats_lock_dir" || return 1
|
||||
stats_lock_owned=1
|
||||
printf '%s\n' "$$" >"${stats_lock_dir}/pid"
|
||||
return 0
|
||||
}
|
||||
|
||||
stats_poll_loop() {
|
||||
local snapshot_interval="$1"
|
||||
local poll_command="$2"
|
||||
local sleep_pid=''
|
||||
|
||||
trap 'if [ -n "$sleep_pid" ]; then kill "$sleep_pid" 2>/dev/null || true; fi; exit 0' HUP INT TERM
|
||||
|
||||
while :; do
|
||||
if ! "$poll_command"; then
|
||||
printf 'error\tdnsmasq_ubus_poll_failed\n'
|
||||
fi
|
||||
|
||||
sleep "$snapshot_interval" &
|
||||
sleep_pid=$!
|
||||
wait "$sleep_pid" 2>/dev/null || true
|
||||
sleep_pid=''
|
||||
done
|
||||
}
|
||||
|
||||
stats_run_collector() {
|
||||
local snapshot_interval="$1"
|
||||
local retention_hours="$2"
|
||||
local lease_file="$3"
|
||||
local persistent_state_file="$4"
|
||||
local persistent_journal_file="$5"
|
||||
local sequence_file="$6"
|
||||
local generation_id ipv6_neigh_command='' ipv6_identity_command='' ipv6_identity_file="${SS_STATSD_IPV6_IDENTITY_FILE:-}" force_rebaseline=0 rc=0
|
||||
|
||||
if [ -f "${SS_STATISTICS_REBASELINE_FILE}" ]; then
|
||||
force_rebaseline=1
|
||||
fi
|
||||
|
||||
# This is only a candidate for a brand-new dataset. The collector modules keep
|
||||
# the generation restored from tmpfs or persistent state when one already exists.
|
||||
generation_id="$(stats_new_generation_id)"
|
||||
if command_exists ip; then
|
||||
ipv6_neigh_command='ip -6 neigh show 2>/dev/null'
|
||||
fi
|
||||
if [ -z "$ipv6_identity_file" ] && [ -x "$SS_STATSD_IDENTITY_COMMAND" ]; then
|
||||
ipv6_identity_command="$SS_STATSD_IDENTITY_COMMAND 2>/dev/null"
|
||||
fi
|
||||
|
||||
stats_fifo="${SS_STATISTICS_DIR}/events.$$"
|
||||
rm -f "$stats_fifo"
|
||||
mkfifo "$stats_fifo" || {
|
||||
log_error "Failed to create statistics FIFO: ${stats_fifo}"
|
||||
return 1
|
||||
}
|
||||
|
||||
stats_poll_loop "$snapshot_interval" "$SS_STATSD_POLL_COMMAND" >"$stats_fifo" &
|
||||
stats_poller_pid=$!
|
||||
|
||||
awk \
|
||||
-v state_file="${SS_STATISTICS_STATE_FILE}" \
|
||||
-v json_file="${SS_STATISTICS_JSON_FILE}" \
|
||||
-v upload_json_file="${SS_STATISTICS_UPLOAD_JSON_FILE}" \
|
||||
-v upload_window_hours="${SS_STATSD_UPLOAD_WINDOW_HOURS}" \
|
||||
-v persistent_state_file="${persistent_state_file}" \
|
||||
-v persistent_journal_file="${persistent_journal_file}" \
|
||||
-v sequence_file="${sequence_file}" \
|
||||
-v snapshot_seq_reservation_size="${SS_STATSD_SEQUENCE_RESERVATION_SIZE}" \
|
||||
-v persistent_interval="${SS_STATSD_PERSISTENT_INTERVAL_S}" \
|
||||
-v persistent_compact_interval="${SS_STATSD_PERSISTENT_COMPACT_INTERVAL_S}" \
|
||||
-v snapshot_interval="${snapshot_interval}" \
|
||||
-v retention_hours="${retention_hours}" \
|
||||
-v lease_file="${lease_file}" \
|
||||
-v ipv6_neigh_command="${ipv6_neigh_command}" \
|
||||
-v ipv6_identity_file="${ipv6_identity_file}" \
|
||||
-v ipv6_identity_command="${ipv6_identity_command}" \
|
||||
-v lease_refresh_interval=60 \
|
||||
-v identity_cache_ttl=60 \
|
||||
-v max_devices=128 \
|
||||
-v generation_seed="${generation_id}" \
|
||||
-v force_rebaseline="${force_rebaseline}" \
|
||||
-v rebaseline_file="${SS_STATISTICS_REBASELINE_FILE}" \
|
||||
-f "$SS_STATSD_AWK_DIR/00-common.awk" \
|
||||
-f "$SS_STATSD_AWK_DIR/10-recovery.awk" \
|
||||
-f "$SS_STATSD_AWK_DIR/20-identity.awk" \
|
||||
-f "$SS_STATSD_AWK_DIR/30-aggregate.awk" \
|
||||
-f "$SS_STATSD_AWK_DIR/40-persistence.awk" \
|
||||
-f "$SS_STATSD_AWK_DIR/50-output.awk" \
|
||||
-f "$SS_STATSD_AWK_DIR/90-main.awk" \
|
||||
<"$stats_fifo" &
|
||||
stats_awk_pid=$!
|
||||
|
||||
wait "$stats_awk_pid" || rc=$?
|
||||
stats_awk_pid=''
|
||||
|
||||
if stats_process_running "$stats_poller_pid"; then
|
||||
kill "$stats_poller_pid" 2>/dev/null || true
|
||||
fi
|
||||
wait "$stats_poller_pid" 2>/dev/null || true
|
||||
stats_poller_pid=''
|
||||
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
main() {
|
||||
local snapshot_interval configured_snapshot_interval retention_hours lease_file configured_lease_file
|
||||
local persistent_state_file persistent_journal_file sequence_file persistence_mode rc=0
|
||||
|
||||
ss_load_config || return 1
|
||||
[ "${ss_enabled}" = "1" ] || return 0
|
||||
[ "${ss_statistics_enabled}" = "1" ] || return 0
|
||||
|
||||
if ! ss_dnsmasq_safeshield_block_supported; then
|
||||
log_warn 'SafeShield statistics collector is disabled because dnsmasq does not provide the SafeShield extension'
|
||||
return 0
|
||||
fi
|
||||
|
||||
[ -x "$SS_STATSD_POLL_COMMAND" ] || {
|
||||
log_error "SafeShield statistics poll command is unavailable: ${SS_STATSD_POLL_COMMAND}"
|
||||
return 1
|
||||
}
|
||||
command_exists mkfifo || {
|
||||
log_error 'mkfifo is required for SafeShield statistics'
|
||||
return 1
|
||||
}
|
||||
|
||||
persistent_state_file="${SS_STATSD_PERSISTENT_STATE_FILE}"
|
||||
persistent_journal_file="${SS_STATSD_PERSISTENT_JOURNAL_FILE}"
|
||||
sequence_file="${SS_STATSD_SEQUENCE_FILE}"
|
||||
persistence_mode='journal'
|
||||
if ! ss_statistics_persistence_enabled; then
|
||||
persistent_state_file=''
|
||||
persistent_journal_file=''
|
||||
sequence_file=''
|
||||
persistence_mode='volatile'
|
||||
fi
|
||||
|
||||
mkdir -p "${SS_STATISTICS_DIR}" || return 1
|
||||
if [ -n "$persistent_state_file" ]; then
|
||||
mkdir -p "${persistent_state_file%/*}" || return 1
|
||||
fi
|
||||
if [ -n "$persistent_journal_file" ]; then
|
||||
mkdir -p "${persistent_journal_file%/*}" || return 1
|
||||
fi
|
||||
if [ -n "$sequence_file" ]; then
|
||||
mkdir -p "${sequence_file%/*}" || return 1
|
||||
fi
|
||||
stats_acquire_lock || return 0
|
||||
|
||||
trap 'stats_cleanup' EXIT
|
||||
trap 'stats_terminate 129' HUP
|
||||
trap 'stats_terminate 130' INT
|
||||
trap 'stats_terminate 143' TERM
|
||||
|
||||
configured_snapshot_interval="${ss_statistics_snapshot_interval_s}"
|
||||
snapshot_interval="$(ss_statistics_effective_snapshot_interval "$configured_snapshot_interval")"
|
||||
retention_hours="${ss_statistics_retention_hours}"
|
||||
lease_file="${SS_STATSD_DHCP_LEASE_FILE:-/tmp/dhcp.leases}"
|
||||
if [ -z "${SS_STATSD_DHCP_LEASE_FILE:-}" ] && command_exists uci; then
|
||||
configured_lease_file="$(uci -q get dhcp.@dnsmasq[0].leasefile 2>/dev/null || true)"
|
||||
[ -n "$configured_lease_file" ] && lease_file="$configured_lease_file"
|
||||
fi
|
||||
|
||||
log_info "Starting statistics collector (source=dnsmasq-ubus snapshot=${snapshot_interval}s configured_snapshot=${configured_snapshot_interval}s retention=${retention_hours}h persistence=${persistence_mode} journal=${SS_STATSD_PERSISTENT_INTERVAL_S}s compact=${SS_STATSD_PERSISTENT_COMPACT_INTERVAL_S}s)"
|
||||
|
||||
stats_run_collector \
|
||||
"$snapshot_interval" \
|
||||
"$retention_hours" \
|
||||
"$lease_file" \
|
||||
"$persistent_state_file" \
|
||||
"$persistent_journal_file" \
|
||||
"$sequence_file" || rc=$?
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in new issue
Block a user