mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 14:23:56 +08:00
🤞 Sync 2026-09-30 23:44:05
This commit is contained in:
1 parent
e3fde93105
commit
44bb740ab2
206 files changed
+5736
-2799
No files matched your search
@@ -12,15 +12,16 @@
|
||||
# Lock: sibling ${FWLIVE_ADAPTIVE_LOCK_FILE:-$STATE.lock} — never the JSON
|
||||
# inode (atomic mv replaces that inode; locking it would not serialize writers).
|
||||
# Lock covers only short update — never held across ubus/filter work.
|
||||
# Hot-path budget: ≤1 flock exec and 2 find execs per successful update
|
||||
# (directory safety checked before lock open and before the state write).
|
||||
# Hot-path budget: ≤1 flock exec and ≤1 find exec per record
|
||||
# (directory safety checked once before the lock is opened; the state write
|
||||
# relies on that check).
|
||||
# Release flock by closing the fd when the subshell exits (no flock -u).
|
||||
# /proc/uptime + state I/O use shell builtins/redirects (no sed/cat/jsonfilter
|
||||
# on the adaptive path). Fail-open on missing
|
||||
# flock, lock busy, or corrupt state. Fail-closed if the lock file cannot be
|
||||
# opened (skip the write; do not record unlocked). Lock-busy ⇒ unlocked
|
||||
# last-writer-wins is acceptable (state stays one valid JSON line; ordering
|
||||
# is not guaranteed).
|
||||
# on the adaptive path). Fail-open on missing flock, a busy lock, or corrupt
|
||||
# state. Fail-closed when the state directory check fails, or when the lock
|
||||
# file cannot be created, opened, or is not a regular writable file (skip the
|
||||
# write; do not record unlocked). Lock-busy ⇒ unlocked last-writer-wins is
|
||||
# acceptable (state stays one valid JSON line; ordering is not guaranteed).
|
||||
# Failed ubus log.read must NOT call record() — a ~0 ms failure is not "cold"
|
||||
# health and must not clear an existing hot/shed cap.
|
||||
# Outside the measured duration interval: plan (pre), record/merge (post).
|
||||
@@ -213,7 +214,6 @@ fwlive_adaptive_write_state() {
|
||||
_w=$4
|
||||
_s=$5
|
||||
_c=$6
|
||||
fwlive_adaptive_state_dir_ok || return 0
|
||||
# Refuse to follow a symlinked state path (same discipline as logging.lock).
|
||||
[ -L "$FWLIVE_ADAPTIVE_STATE_FILE" ] && return 0
|
||||
_tmp="${FWLIVE_ADAPTIVE_STATE_FILE}.tmp.$$"
|
||||
@@ -229,36 +229,32 @@ fwlive_adaptive_write_state() {
|
||||
return 0
|
||||
}
|
||||
|
||||
# One non-blocking flock on the persistent sibling lock file; fail-open if
|
||||
# missing/busy. Release by exiting the subshell (closes fd 9) — no flock -u.
|
||||
# Busy ⇒ run unlocked: last-writer-wins is acceptable (valid one-line JSON;
|
||||
# ordering under contention is not guaranteed).
|
||||
# Lock-open failure (redirection): fail-closed — skip the write; do not run
|
||||
# record() unlocked. Probe fd 9 first so a set -eu caller does not abort.
|
||||
# One non-blocking flock on the persistent sibling lock file.
|
||||
# Fail-open only when flock is missing or the lock is busy (unlocked
|
||||
# last-writer-wins: valid one-line JSON; ordering is not guaranteed).
|
||||
# Fail-closed — skip the body — when the state directory is unsafe, or the
|
||||
# lock cannot be created, opened, or is not a regular writable file.
|
||||
# Release by exiting the subshell (closes fd 9) — no flock -u.
|
||||
# Probe fd 9 first so a set -eu caller does not abort.
|
||||
fwlive_adaptive_with_lock() {
|
||||
fwlive_adaptive_state_dir_ok || return 0
|
||||
if ! command -v flock >/dev/null 2>&1; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
fwlive_adaptive_state_dir_ok || {
|
||||
"$@"
|
||||
return $?
|
||||
}
|
||||
_lock=$(fwlive_adaptive_lock_path)
|
||||
# Symlinked or non-file lock path: do not create/follow; fail open unlocked.
|
||||
if [ -L "$_lock" ] || [ -d "$_lock" ]; then
|
||||
"$@"
|
||||
return $?
|
||||
if [ -L "$_lock" ]; then
|
||||
return 0
|
||||
fi
|
||||
# Create lock at 0600 (world-readable fd can take LOCK_EX).
|
||||
if [ ! -e "$_lock" ]; then
|
||||
if ! ( umask 077; : >"$_lock" ) 2>/dev/null; then
|
||||
"$@"
|
||||
return $?
|
||||
return 0
|
||||
fi
|
||||
elif [ ! -w "$_lock" ]; then
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
# Only a regular file: a writable FIFO would block the fd-9 open below.
|
||||
if [ ! -f "$_lock" ] || [ ! -w "$_lock" ]; then
|
||||
return 0
|
||||
fi
|
||||
# Probe in a subshell first: a failed fd-9 redirection aborts a POSIX
|
||||
# non-interactive shell (set -eu) before any fallback can run.
|
||||
@@ -270,7 +266,6 @@ fwlive_adaptive_with_lock() {
|
||||
"$@"
|
||||
exit $?
|
||||
fi
|
||||
# Lock busy — fail open (unlocked).
|
||||
"$@"
|
||||
) 9>>"$_lock"
|
||||
return $?
|
||||
@@ -290,6 +285,22 @@ fwlive_adaptive_bucket_for_ms() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Clamp $_limit into [1, min($1, $2)].
|
||||
fwlive_adaptive_clamp() {
|
||||
[ "$_limit" -gt "$1" ] && _limit=$1
|
||||
[ "$_limit" -gt "$2" ] && _limit=$2
|
||||
[ "$_limit" -lt 1 ] && _limit=1
|
||||
return 0
|
||||
}
|
||||
|
||||
# Double $_limit. A base below 1 becomes $1; the result is capped at $1.
|
||||
fwlive_adaptive_double() {
|
||||
[ "$_limit" -lt 1 ] && _limit=$1
|
||||
_limit=$((_limit * 2))
|
||||
[ "$_limit" -gt "$1" ] && _limit=$1
|
||||
return 0
|
||||
}
|
||||
|
||||
# Args: requested_limit duration_ms prev_limit prev_bucket prev_warm
|
||||
# prev_completed_cs planning
|
||||
# Prints next limit. A full-sized cold sample may retain one upward probe
|
||||
@@ -307,6 +318,7 @@ fwlive_adaptive_compute_limit() {
|
||||
_bucket=$(fwlive_adaptive_bucket_for_ms "$_ms")
|
||||
_limit=$_max
|
||||
_cd=0
|
||||
_probe=0
|
||||
|
||||
case "$_prev_b" in
|
||||
hot) _cd=$FWLIVE_ADAPTIVE_COOLDOWN_HOT_CS ;;
|
||||
@@ -316,44 +328,30 @@ fwlive_adaptive_compute_limit() {
|
||||
|
||||
if [ "$_cd" -gt 0 ] && [ "$_prev_c" -gt 0 ] && \
|
||||
[ "$_elapsed" -lt "$_cd" ]; then
|
||||
if [ "$_ms" -le "$FWLIVE_ADAPTIVE_HOT_EXIT_MS" ] && \
|
||||
[ "$_bucket" != hot ]; then
|
||||
:
|
||||
else
|
||||
if [ "$_ms" -gt "$FWLIVE_ADAPTIVE_HOT_EXIT_MS" ] || \
|
||||
[ "$_bucket" = hot ]; then
|
||||
_limit=$_prev_l
|
||||
[ "$_limit" -gt "$_req" ] && _limit=$_req
|
||||
[ "$_limit" -gt "$_max" ] && _limit=$_max
|
||||
[ "$_limit" -lt 1 ] && _limit=1
|
||||
fwlive_adaptive_clamp "$_req" "$_max"
|
||||
printf '%s\n' "$_limit"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Once a hot/warm cooldown expires, deliberately test a larger request.
|
||||
# Planning and recording use the same pure helper: planning raises the next
|
||||
# request, while recording raises the retained limit only when that probe
|
||||
# completed below the hot-exit threshold. A still-hot probe therefore falls
|
||||
# through to the hot floor below.
|
||||
# Planning raises the next request. Recording raises the retained limit
|
||||
# only when that probe completed below the hot-exit threshold. A still-hot
|
||||
# probe falls through to the hot floor below.
|
||||
if [ "$_cd" -gt 0 ] && [ "$_prev_c" -gt 0 ] && \
|
||||
[ "$_elapsed" -ge "$_cd" ]; then
|
||||
if [ "$_planning" = 1 ]; then
|
||||
_limit=$_prev_l
|
||||
[ "$_limit" -lt 1 ] && _limit=$_max
|
||||
_limit=$((_limit * 2))
|
||||
[ "$_limit" -gt "$_max" ] && _limit=$_max
|
||||
[ "$_limit" -gt "$_req" ] && _limit=$_req
|
||||
[ "$_limit" -lt 1 ] && _limit=1
|
||||
printf '%s\n' "$_limit"
|
||||
return 0
|
||||
fi
|
||||
_healthy=0
|
||||
if [ "$_ms" -le "$FWLIVE_ADAPTIVE_HOT_EXIT_MS" ] && \
|
||||
[ "$_bucket" != hot ]; then
|
||||
_healthy=1
|
||||
fi
|
||||
if [ "$_planning" = 1 ] || [ "$_healthy" = 1 ]; then
|
||||
_limit=$_prev_l
|
||||
[ "$_limit" -lt 1 ] && _limit=$_max
|
||||
_limit=$((_limit * 2))
|
||||
[ "$_limit" -gt "$_max" ] && _limit=$_max
|
||||
[ "$_limit" -gt "$_req" ] && _limit=$_req
|
||||
[ "$_limit" -lt 1 ] && _limit=1
|
||||
fwlive_adaptive_double "$_max"
|
||||
fwlive_adaptive_clamp "$_req" "$_max"
|
||||
printf '%s\n' "$_limit"
|
||||
return 0
|
||||
fi
|
||||
@@ -369,7 +367,9 @@ fwlive_adaptive_compute_limit() {
|
||||
elif [ "$_req" -ge "$_base" ]; then
|
||||
# A full-sized cold sample earns one doubling, not a jump
|
||||
# from the 250-line floor straight to the maximum.
|
||||
_limit=$((_base * 2))
|
||||
_limit=$_base
|
||||
fwlive_adaptive_double "$_max"
|
||||
[ "$_prev_l" -ge 1 ] && _probe=1
|
||||
else
|
||||
# A short cold sample does not prove the larger request is safe.
|
||||
_limit=$_base
|
||||
@@ -381,7 +381,7 @@ fwlive_adaptive_compute_limit() {
|
||||
# 250-line cap.
|
||||
if [ "$_prev_l" -gt "$FWLIVE_ADAPTIVE_COOL_CAP" ] && \
|
||||
[ "$_prev_c" -gt 0 ] && \
|
||||
[ "$((_now - _prev_c))" -lt "$FWLIVE_ADAPTIVE_COOLDOWN_PROBE_CS" ]; then
|
||||
[ "$_elapsed" -lt "$FWLIVE_ADAPTIVE_COOLDOWN_PROBE_CS" ]; then
|
||||
_limit=$_prev_l
|
||||
else
|
||||
_limit=$FWLIVE_ADAPTIVE_COOL_CAP
|
||||
@@ -401,12 +401,11 @@ fwlive_adaptive_compute_limit() {
|
||||
hot) _limit=$FWLIVE_ADAPTIVE_HOT_FLOOR ;;
|
||||
esac
|
||||
|
||||
if [ "$_planning" = 1 ] || [ "$_bucket" != cold ] || \
|
||||
[ "$_prev_l" -lt 1 ] || [ "$_req" -lt "$_prev_l" ]; then
|
||||
[ "$_limit" -gt "$_req" ] && _limit=$_req
|
||||
if [ "$_probe" = 1 ]; then
|
||||
fwlive_adaptive_clamp "$_max" "$_max"
|
||||
else
|
||||
fwlive_adaptive_clamp "$_req" "$_max"
|
||||
fi
|
||||
[ "$_limit" -gt "$_max" ] && _limit=$_max
|
||||
[ "$_limit" -lt 1 ] && _limit=1
|
||||
printf '%s\n' "$_limit"
|
||||
}
|
||||
|
||||
@@ -498,34 +497,19 @@ fwlive_adaptive_merge_reply() {
|
||||
*) printf '%s' "$_body"; return 0 ;;
|
||||
esac
|
||||
_base=${_body%\}}
|
||||
_effective=
|
||||
[ "$_success" = 1 ] && _effective=",\"effective_limit\":$_limit"
|
||||
_has_msgs=0
|
||||
_tail="\"adaptive\":$_adapt"
|
||||
case "$_body" in
|
||||
*',"messages_received":'*) _has_msgs=1 ;;
|
||||
*',"messages_received":'*) ;;
|
||||
*) _tail="${_tail},\"messages_received\":$_msgs" ;;
|
||||
esac
|
||||
if [ "$_adapt" = 0 ]; then
|
||||
if [ "$_has_msgs" = 1 ]; then
|
||||
printf '%s%s"adaptive":0}' "$_base" "$_sep"
|
||||
else
|
||||
printf '%s%s"adaptive":0,"messages_received":%s}' "$_base" "$_sep" "$_msgs"
|
||||
if [ "$_adapt" = 1 ]; then
|
||||
if [ "$_success" = 1 ]; then
|
||||
_tail="${_tail},\"effective_limit\":$_limit"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
if [ "$_shed" = 1 ]; then
|
||||
if [ "$_has_msgs" = 1 ]; then
|
||||
printf '%s%s"adaptive":1%s,"truncated":%s,"shed":{"level":"hot","limit":%s}}' \
|
||||
"$_base" "$_sep" "$_effective" "$_trunc" "$_limit"
|
||||
else
|
||||
printf '%s%s"adaptive":1,"messages_received":%s%s,"truncated":%s,"shed":{"level":"hot","limit":%s}}' \
|
||||
"$_base" "$_sep" "$_msgs" "$_effective" "$_trunc" "$_limit"
|
||||
fi
|
||||
else
|
||||
if [ "$_has_msgs" = 1 ]; then
|
||||
printf '%s%s"adaptive":1%s,"truncated":%s}' "$_base" "$_sep" "$_effective" "$_trunc"
|
||||
else
|
||||
printf '%s%s"adaptive":1,"messages_received":%s%s,"truncated":%s}' \
|
||||
"$_base" "$_sep" "$_msgs" "$_effective" "$_trunc"
|
||||
_tail="${_tail},\"truncated\":$_trunc"
|
||||
if [ "$_shed" = 1 ]; then
|
||||
_tail="${_tail},\"shed\":{\"level\":\"hot\",\"limit\":$_limit}"
|
||||
fi
|
||||
fi
|
||||
printf '%s%s%s}' "$_base" "$_sep" "$_tail"
|
||||
}
|
||||
@@ -268,10 +268,14 @@ wan_log_generation_bump() {
|
||||
printf '%s' "$_generation_next"
|
||||
}
|
||||
|
||||
wan_log_error_json() {
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"%s"}' "$1" "$2"
|
||||
}
|
||||
|
||||
wan_log_tracking_failed_json() {
|
||||
_zone_json="$1"
|
||||
logger -t fwlive "WAN log toggle aborted: commit generation unavailable" 2>/dev/null || true
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"rollback_tracking_failed"}' "$_zone_json"
|
||||
wan_log_error_json "$_zone_json" rollback_tracking_failed
|
||||
}
|
||||
|
||||
find_wan_zone_section_state() {
|
||||
@@ -940,7 +944,7 @@ commit_wan_log_change() {
|
||||
# non-empty changes list here can only be a foreign writer's race.
|
||||
if firewall_changes_pending; then
|
||||
logger -t fwlive "WAN log toggle aborted at commit gate: firewall changes staged by another writer" 2>/dev/null || true
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"firewall_changes_pending"}' "$zone_json"
|
||||
wan_log_error_json "$zone_json" firewall_changes_pending
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -960,12 +964,12 @@ commit_wan_log_change() {
|
||||
|
||||
if [ -n "$target" ]; then
|
||||
if ! uci set "firewall.${zone}.log=${target}"; then
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"uci_set_failed"}' "$zone_json"
|
||||
wan_log_error_json "$zone_json" uci_set_failed
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
if ! uci delete "firewall.${zone}.log"; then
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"uci_delete_failed"}' "$zone_json"
|
||||
wan_log_error_json "$zone_json" uci_delete_failed
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
@@ -982,7 +986,7 @@ commit_wan_log_change() {
|
||||
uci set "firewall.${zone}.log=${previous}" 2>/dev/null || true
|
||||
fi
|
||||
logger -t fwlive "WAN log toggle aborted after stage: firewall changes staged by another writer" 2>/dev/null || true
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"firewall_changes_pending"}' "$zone_json"
|
||||
wan_log_error_json "$zone_json" firewall_changes_pending
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -1001,7 +1005,7 @@ commit_wan_log_change() {
|
||||
else
|
||||
logger -t fwlive "WAN log commit failed with foreign changes staged — not reverting (uci_commit_failed)" 2>/dev/null || true
|
||||
fi
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"uci_commit_failed"}' "$zone_json"
|
||||
wan_log_error_json "$zone_json" uci_commit_failed
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -1036,14 +1040,14 @@ report_wan_log_after_commit() {
|
||||
if [ "$_rc" -eq 2 ]; then
|
||||
if ! reload_firewall; then
|
||||
logger -t fwlive "Firewall reload failed after raced WAN log commit" 2>/dev/null || true
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"firewall_reload_failed"}' "$zone_json"
|
||||
wan_log_error_json "$zone_json" firewall_reload_failed
|
||||
return 0
|
||||
fi
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"firewall_commit_raced"}' "$zone_json"
|
||||
wan_log_error_json "$zone_json" firewall_commit_raced
|
||||
return 0
|
||||
fi
|
||||
shift 2
|
||||
reload_and_report_wan_log "$@" "$WAN_LOG_COMMIT_GENERATION"
|
||||
reload_and_report_wan_log "$@" "$zone_json" "$WAN_LOG_COMMIT_GENERATION"
|
||||
}
|
||||
|
||||
# Firewall reload + best-effort UCI rollback on reload failure. The reload
|
||||
@@ -1095,7 +1099,7 @@ reload_and_report_wan_log() {
|
||||
# reload failure. The next toggle self-corrects the state.
|
||||
logger -t fwlive "Firewall reload failed; rollback lock unavailable — skipped" 2>/dev/null || true
|
||||
fi
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"firewall_reload_failed"}' "$zone_json"
|
||||
wan_log_error_json "$zone_json" firewall_reload_failed
|
||||
return 0
|
||||
fi
|
||||
logger -t fwlive "$success_msg" 2>/dev/null || true
|
||||
@@ -1103,40 +1107,44 @@ reload_and_report_wan_log() {
|
||||
return 0
|
||||
}
|
||||
|
||||
enable_wan_logging() {
|
||||
# $1=enable checks nf_log before the lock. Success leaves the lock held
|
||||
# and zone / zone_json set. Failure prints JSON and returns non-zero.
|
||||
wan_log_toggle_preamble() {
|
||||
find_wan_zone_section_state
|
||||
zone=$WAN_ZONE_FOUND
|
||||
if [ -z "$zone" ]; then
|
||||
no_wan_zone_error_json
|
||||
return 0
|
||||
return 1
|
||||
fi
|
||||
|
||||
zone_json=$(json_null_or_string "$zone")
|
||||
|
||||
NF_LOG_STATE_COMPUTED=0
|
||||
compute_nf_log_state
|
||||
if [ "$NF_LOG_IPV4_READY" != true ] || [ "$NF_LOG_IPV6_READY" != true ]; then
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"nf_log_missing"}' "$zone_json"
|
||||
return 0
|
||||
if [ "${1-}" = enable ]; then
|
||||
NF_LOG_STATE_COMPUTED=0
|
||||
compute_nf_log_state
|
||||
if [ "$NF_LOG_IPV4_READY" != true ] || [ "$NF_LOG_IPV6_READY" != true ]; then
|
||||
wan_log_error_json "$zone_json" nf_log_missing
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Locked critical section: read->compute->stage->commit for firewall.<zone>.log.
|
||||
# The log bit is re-read AFTER acquiring the lock so the target is computed
|
||||
# from the latest committed value; a concurrent toggle cannot interleave.
|
||||
# Staging + commit live inside commit_wan_log_change behind its last-moment
|
||||
# firewall_changes_pending re-check: a foreign writer racing between
|
||||
# the early check above and the commit aborts the toggle instead of having
|
||||
# the pending check below and the commit aborts the toggle instead of having
|
||||
# its half-finished delta published with our log bit.
|
||||
if ! acquire_wan_log_lock; then
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"lock_failed"}' "$zone_json"
|
||||
return 0
|
||||
wan_log_error_json "$zone_json" lock_failed
|
||||
return 1
|
||||
fi
|
||||
|
||||
if firewall_changes_pending; then
|
||||
release_wan_log_lock
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"firewall_changes_pending"}' "$zone_json"
|
||||
return 0
|
||||
wan_log_error_json "$zone_json" firewall_changes_pending
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
enable_wan_logging() {
|
||||
wan_log_toggle_preamble enable || return 0
|
||||
|
||||
current=$(wan_zone_log_value "$zone")
|
||||
if wan_filter_log_enabled "$current"; then
|
||||
@@ -1157,7 +1165,7 @@ enable_wan_logging() {
|
||||
|
||||
if ! maybe_snapshot_wan_log_baseline "$zone"; then
|
||||
release_wan_log_lock
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"baseline_snapshot_failed"}' "$zone_json"
|
||||
wan_log_error_json "$zone_json" baseline_snapshot_failed
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -1168,38 +1176,12 @@ enable_wan_logging() {
|
||||
report_wan_log_after_commit "$_commit_rc" "$zone_json" \
|
||||
"$zone" "$current" "$target" \
|
||||
'Firewall reload failed after enable; reverted UCI WAN log' \
|
||||
'WAN zone logging enabled' \
|
||||
"$zone_json"
|
||||
'WAN zone logging enabled'
|
||||
return 0
|
||||
}
|
||||
|
||||
disable_wan_logging() {
|
||||
find_wan_zone_section_state
|
||||
zone=$WAN_ZONE_FOUND
|
||||
if [ -z "$zone" ]; then
|
||||
no_wan_zone_error_json
|
||||
return 0
|
||||
fi
|
||||
|
||||
zone_json=$(json_null_or_string "$zone")
|
||||
|
||||
# Locked critical section: read->compute->stage->commit for firewall.<zone>.log.
|
||||
# The log bit is re-read AFTER acquiring the lock so the target is computed
|
||||
# from the latest committed value; a concurrent toggle cannot interleave.
|
||||
# Staging + commit live inside commit_wan_log_change behind its last-moment
|
||||
# firewall_changes_pending re-check: a foreign writer racing between
|
||||
# the early check above and the commit aborts the toggle instead of having
|
||||
# its half-finished delta published with our log bit.
|
||||
if ! acquire_wan_log_lock; then
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"lock_failed"}' "$zone_json"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if firewall_changes_pending; then
|
||||
release_wan_log_lock
|
||||
printf '{"ok":false,"changed":false,"wan_zone":%s,"error":"firewall_changes_pending"}' "$zone_json"
|
||||
return 0
|
||||
fi
|
||||
wan_log_toggle_preamble || return 0
|
||||
|
||||
current=$(wan_zone_log_value "$zone")
|
||||
if [ -z "$current" ] || ! wan_filter_log_enabled "$current"; then
|
||||
@@ -1224,8 +1206,7 @@ disable_wan_logging() {
|
||||
report_wan_log_after_commit "$_commit_rc" "$zone_json" \
|
||||
"$zone" "$current" "$target" \
|
||||
'Firewall reload failed after disable; reverted UCI WAN log' \
|
||||
'WAN zone logging disabled' \
|
||||
"$zone_json"
|
||||
'WAN zone logging disabled'
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ LOGGING_SH="$LIBEXEC_DIR/fwlive-logging.sh"
|
||||
ADAPTIVE_SH="$LIBEXEC_DIR/fwlive-adaptive-cap.sh"
|
||||
# Max reverse-DNS lookups per resolve call (~32 names keeps nslookup load bounded on small routers).
|
||||
RESOLVE_MAX=32
|
||||
# DNS names are at most 253 octets; a longer PTR answer is treated as no name.
|
||||
RESOLVE_NAME_MAX=253
|
||||
# Per-lookup nslookup timeout (integer seconds — BusyBox `timeout` truncates
|
||||
# fractional values to 0 on OpenWrt 23.05/1.36, so 0.5 would silently
|
||||
# degrade to no timeout; luna fold 2026-08-10). The wall-clock budget
|
||||
@@ -70,6 +72,15 @@ RULES_MAP_MAX_BYTES=65536
|
||||
# the whole dump. Bound map_prefix_with_label calls independently.
|
||||
RULES_MAP_MAX_LINES=512
|
||||
|
||||
# Append one already-escaped "key":"value" to OUT. Value may be empty.
|
||||
# No first-wins dedupe and no RULES_MAP_MAX_* caps.
|
||||
json_object_append() {
|
||||
if [ -n "$OUT" ]; then
|
||||
OUT="${OUT},"
|
||||
fi
|
||||
OUT="${OUT}\"$1\":\"$2\""
|
||||
}
|
||||
|
||||
map_add() {
|
||||
key="$1"
|
||||
val="$2"
|
||||
@@ -105,10 +116,7 @@ map_add() {
|
||||
return 0
|
||||
fi
|
||||
_map_keys=$(( ${_map_keys:-0} + 1 ))
|
||||
if [ -n "$OUT" ]; then
|
||||
OUT="${OUT},"
|
||||
fi
|
||||
OUT="${OUT}\"${esc_key}\":\"${esc_val}\""
|
||||
json_object_append "$esc_key" "$esc_val"
|
||||
}
|
||||
|
||||
uci_rule_names() {
|
||||
@@ -581,10 +589,12 @@ build_rules_map() {
|
||||
_enrich_error='no_backend'
|
||||
fi
|
||||
_rules_json="{\"backend\":\"$backend\",\"rules\":{$OUT}"
|
||||
# Map limits and enrichment failures are independent outcomes.
|
||||
_truncated=false
|
||||
[ -n "$_map_overflow" ] && _truncated=true
|
||||
_rules_json="${_rules_json},\"truncated\":${_truncated}"
|
||||
if [ -n "$_enrich_error" ]; then
|
||||
_rules_json="${_rules_json},\"error\":\"${_enrich_error}\""
|
||||
elif [ -n "$_map_overflow" ]; then
|
||||
_rules_json="${_rules_json},\"error\":\"rules_truncated\""
|
||||
fi
|
||||
printf '%s}' "$_rules_json"
|
||||
}
|
||||
@@ -617,6 +627,22 @@ poll_clamp_lines() {
|
||||
fi
|
||||
}
|
||||
|
||||
# jshn_load_input JSON
|
||||
# Status 0: JSON loaded (json_* available). Status 2: library missing
|
||||
# (FWLIVE_JSHN_SH or json_init). Status 1: parse failure.
|
||||
# Caller checks command -v jshn (jshn_missing) and runs with nounset off.
|
||||
# jshn uses intentionally unset state variables.
|
||||
jshn_load_input() {
|
||||
# shellcheck disable=SC1090,SC1091
|
||||
. "${FWLIVE_JSHN_SH:-/usr/share/libubox/jshn.sh}" 2>/dev/null || return 2
|
||||
json_init >/dev/null 2>&1 || return 2
|
||||
# json_load masks a failing jshn exit through eval. Check the real
|
||||
# parser once, then evaluate only its generated shell assignments.
|
||||
_json_no_warning=1
|
||||
_json_code=$(jshn -r "$1" 2>/dev/null) || return 1
|
||||
eval "$_json_code" || return 1
|
||||
}
|
||||
|
||||
# Failed jshn reads still default to 50 (silent numeric contract; syslog named cause only).
|
||||
poll_lines_from_input() {
|
||||
input="$1"
|
||||
@@ -627,20 +653,12 @@ poll_lines_from_input() {
|
||||
_cause=jshn_missing
|
||||
else
|
||||
_rc=0
|
||||
# jshn uses intentionally unset state variables. Keep nounset disabled in
|
||||
# this subshell so strict mode is restored automatically on every exit.
|
||||
# exit 2: library missing or source failed; exit 1: JSON parse failed.
|
||||
# Keep nounset disabled in this subshell so strict mode is restored
|
||||
# on every exit. Status 2: library missing; status 1: parse failure.
|
||||
# Missing addresses[0] is an intentional silent default, not a parse error.
|
||||
_first=$( (
|
||||
set +u
|
||||
# shellcheck disable=SC1090,SC1091
|
||||
. "${FWLIVE_JSHN_SH:-/usr/share/libubox/jshn.sh}" 2>/dev/null || exit 2
|
||||
json_init >/dev/null 2>&1 || exit 2
|
||||
# json_load masks a failing jshn exit through eval. Check the real
|
||||
# parser once, then evaluate only its generated shell assignments.
|
||||
_json_no_warning=1
|
||||
_json_code=$(jshn -r "$input" 2>/dev/null) || exit 1
|
||||
eval "$_json_code" || exit 1
|
||||
jshn_load_input "$input" || exit $?
|
||||
first=
|
||||
if json_select addresses 2>/dev/null; then
|
||||
json_get_var first 1 || first=
|
||||
@@ -928,15 +946,10 @@ resolve_addresses() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
_rc=0
|
||||
_addresses=$( (
|
||||
set +u
|
||||
# shellcheck disable=SC1090,SC1091
|
||||
. "${FWLIVE_JSHN_SH:-/usr/share/libubox/jshn.sh}"
|
||||
# json_load masks a failing jshn exit through eval. Check the real
|
||||
# parser once, then evaluate only its generated shell assignments.
|
||||
_json_no_warning=1
|
||||
_json_code=$(jshn -r "$input" 2>/dev/null) || exit 1
|
||||
eval "$_json_code" || exit 1
|
||||
jshn_load_input "$input" || exit $?
|
||||
if ! json_select addresses 2>/dev/null; then exit 0; fi
|
||||
idx=1
|
||||
# json_get_type false ends the array. A non-string element is skipped
|
||||
@@ -955,49 +968,44 @@ resolve_addresses() {
|
||||
fi
|
||||
idx=$((idx + 1))
|
||||
done
|
||||
) ) || {
|
||||
) ) || _rc=$?
|
||||
if [ "$_rc" -eq 2 ]; then
|
||||
printf '{"names":{},"error":"jshn_lib_missing"}'
|
||||
return 0
|
||||
fi
|
||||
if [ "$_rc" -ne 0 ]; then
|
||||
printf '{"names":{},"error":"invalid_input"}'
|
||||
return 0
|
||||
}
|
||||
fi
|
||||
while IFS= read -r ip; do
|
||||
[ -n "${ip:-}" ] || continue
|
||||
# Producer already validated; skip repeats before they cost a slot.
|
||||
case "$OUT" in
|
||||
*"\"$ip\":"*) continue ;;
|
||||
esac
|
||||
if [ "$lookups" -ge "$RESOLVE_MAX" ]; then
|
||||
truncated=1
|
||||
break
|
||||
fi
|
||||
# Abort cleanly once the lookup-loop wall-clock budget is spent;
|
||||
# never START a lookup past the budget.
|
||||
now=$(fwlive_adaptive_clock_cs)
|
||||
if [ $((now - start)) -ge $((RESOLVE_BUDGET * 100)) ]; then
|
||||
truncated=1
|
||||
break
|
||||
fi
|
||||
lookups=$((lookups + 1))
|
||||
# NXDOMAIN / timeout: resolve_hostname returns 1. set -e
|
||||
# cannot apply to this assignment.
|
||||
name=$(resolve_hostname "$ip") || name=
|
||||
if [ -n "$name" ]; then
|
||||
map_add "$ip" "$name"
|
||||
else
|
||||
# Record a completed miss so the view can fail-mark NXDOMAIN
|
||||
# without treating the unprocessed truncated tail as tried.
|
||||
# map_add skips empty values (rules-map labels); resolve needs them.
|
||||
esc_key=$(printf '%s' "$ip" | json_escape)
|
||||
case "$OUT" in
|
||||
*"\"$esc_key\":"*) ;;
|
||||
*)
|
||||
if [ -n "$OUT" ]; then
|
||||
OUT="${OUT},"
|
||||
fi
|
||||
OUT="${OUT}\"${esc_key}\":\"\""
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done <<EOF
|
||||
[ -n "${ip:-}" ] || continue
|
||||
# Producer already validated; skip repeats before they cost a slot.
|
||||
case "$OUT" in
|
||||
*"\"$ip\":"*) continue ;;
|
||||
esac
|
||||
if [ "$lookups" -ge "$RESOLVE_MAX" ]; then
|
||||
truncated=1
|
||||
break
|
||||
fi
|
||||
# Abort cleanly once the lookup-loop wall-clock budget is spent;
|
||||
# never START a lookup past the budget.
|
||||
now=$(fwlive_adaptive_clock_cs)
|
||||
if [ $((now - start)) -ge $((RESOLVE_BUDGET * 100)) ]; then
|
||||
truncated=1
|
||||
break
|
||||
fi
|
||||
lookups=$((lookups + 1))
|
||||
# NXDOMAIN / timeout: resolve_hostname returns 1. set -e
|
||||
# cannot apply to this assignment.
|
||||
name=$(resolve_hostname "$ip") || name=
|
||||
[ "${#name}" -le "$RESOLVE_NAME_MAX" ] || name=
|
||||
esc_key=$(printf '%s' "$ip" | json_escape)
|
||||
esc_val=
|
||||
if [ -n "$name" ]; then
|
||||
esc_val=$(printf '%s' "$name" | json_escape)
|
||||
fi
|
||||
json_object_append "$esc_key" "$esc_val"
|
||||
done <<EOF
|
||||
$_addresses
|
||||
EOF
|
||||
|
||||
@@ -1420,6 +1428,12 @@ case "${1:-}" in
|
||||
run_selftest
|
||||
exit $?
|
||||
;;
|
||||
__limits)
|
||||
printf '%s %s %s %s %s\n' \
|
||||
"$POLL_TIMEOUT" "$TIMEOUT_KILL_GRACE" "$NFT_TIMEOUT" \
|
||||
"$RESOLVE_TIMEOUT" "$RESOLVE_BUDGET"
|
||||
exit 0
|
||||
;;
|
||||
__poll_clamp)
|
||||
poll_clamp_lines "${2:-}"
|
||||
exit $?
|
||||
|
||||
Reference in new issue
Block a user