mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 16:44:23 +08:00
🎨 Sync 2026-09-15 23:44:06
This commit is contained in:
1 parent
7a06102934
commit
845f5b3078
19 files changed
+130
-152
No files matched your search
@@ -2,7 +2,7 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright 2025-2026 Lucas Albers <lucas.b.albers@gmail.com>
|
||||
#
|
||||
# Layer 1 adaptive poll cap (#306). Sourced by rpcd/fwlive.
|
||||
# Layer 1 adaptive poll cap. Sourced by rpcd/fwlive.
|
||||
# Always on unless test/triage override (no UCI / no product config):
|
||||
# FWLIVE_ADAPTIVE=0|false|off|no
|
||||
# or sentinel ${FWLIVE_ADAPTIVE_OFF_FILE:-<state-dir>/fwlive-adaptive-off}
|
||||
@@ -18,10 +18,10 @@
|
||||
# flock, lock busy, or corrupt state. 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 (#329 Hermes Q1).
|
||||
# health and must not clear an existing hot/shed cap.
|
||||
# Outside the measured duration interval: plan (pre), record/merge (post).
|
||||
# messages_received is 0 in Layer 1 — do not ash-scan the filter JSON after
|
||||
# end_cs (Grok #329 P1); accurate count belongs in the filter or a later layer.
|
||||
# end_cs; accurate count belongs in the filter or a later layer.
|
||||
|
||||
FWLIVE_ADAPTIVE_STATE_FILE="${FWLIVE_ADAPTIVE_STATE_FILE:-/var/run/fwlive-state.json}"
|
||||
# Optional overrides; when unset, lock/off paths are siblings of the current state.
|
||||
@@ -238,7 +238,7 @@ fwlive_adaptive_with_lock() {
|
||||
"$@"
|
||||
return $?
|
||||
fi
|
||||
# Create lock at 0600 (logging.lock #167 — world-readable fd can take LOCK_EX).
|
||||
# Create lock at 0600 (world-readable fd can take LOCK_EX).
|
||||
if [ ! -e "$_lock" ]; then
|
||||
if ! ( umask 077; : >"$_lock" ) 2>/dev/null; then
|
||||
"$@"
|
||||
@@ -424,7 +424,7 @@ fwlive_adaptive_merge_reply() {
|
||||
}
|
||||
|
||||
# Count log[*] via "msg" keys — helper for tests / future filter-side count.
|
||||
# NOT called on the poll hot path (Grok #329 P1): ash-scanning a 2000-entry
|
||||
# NOT called on the poll hot path: ash-scanning a 2000-entry
|
||||
# reply after end_cs is unmeasured overhead that cannot shed itself.
|
||||
fwlive_adaptive_count_log() {
|
||||
_json=$1
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
# Log messages are treated as data (jsonfilter + awk stdin); never interpolated
|
||||
# into shell command strings. Usage: ubus call log read '...' | fwlive-log-filter.sh
|
||||
#
|
||||
# Perf (#219): one jsonfilter for @.log[*] plus one awk classify. Process
|
||||
# Perf: one jsonfilter for @.log[*] plus one awk classify. Process
|
||||
# count is constant per poll, not O(entries).
|
||||
#
|
||||
# Entry point (pipeline). The classifier sibling is sourced and must not set
|
||||
# strict mode itself (#291 C3).
|
||||
# strict mode itself.
|
||||
set -eu
|
||||
# shellcheck disable=SC3040
|
||||
(set -o pipefail) 2>/dev/null && set -o pipefail
|
||||
@@ -26,7 +26,7 @@ FILTER_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
. "$FILTER_DIR/fwlive-is-firewall-event.sh"
|
||||
|
||||
# The classifier is a package asset, not generated at request time. Fail
|
||||
# closed if an incomplete install or a damaged package removed it (#321).
|
||||
# closed if an incomplete install or a damaged package removed it.
|
||||
if [ ! -r "$CLASSIFY_AWK" ]; then
|
||||
printf '%s' '{"log":[],"error":"classifier_missing"}'
|
||||
exit 1
|
||||
@@ -35,8 +35,8 @@ fi
|
||||
printf '%s' '{"log":['
|
||||
# Prefer stdin over -s: Linux MAX_ARG_STRLEN is 128KiB; a raised logd ring
|
||||
# (or paused FETCH_LINES_MAX poll) can exceed that and make jsonfilter fail
|
||||
# while this script still printed {"log":[]} and exited 0 (#234).
|
||||
# jsonfilter miss / empty @.log is not fatal; must still close JSON (#220).
|
||||
# set -e + pipefail cannot apply to this pipeline (#291 C3).
|
||||
# while this script still printed {"log":[]} and exited 0.
|
||||
# jsonfilter miss / empty @.log is not fatal; must still close JSON.
|
||||
# set -e + pipefail cannot apply to this pipeline.
|
||||
jsonfilter -e '@.log[*]' 2>/dev/null | _fwlive_filter_json_entries || true
|
||||
printf '%s' ']}'
|
||||
@@ -7,13 +7,13 @@
|
||||
# Sourced library (rpcd plugin, package prerm). Do not `set -euo pipefail`
|
||||
# here: prerm is best-effort (`restore ... || logger`; exit 0) and callers
|
||||
# expect soft failures. The rpcd entry point enables strict mode; critical
|
||||
# paths use explicit `|| return 1` / `|| true` (#244, #291 C3).
|
||||
# paths use explicit `|| return 1` / `|| true`.
|
||||
|
||||
NF_LOG_IPV4='/proc/sys/net/netfilter/nf_log/2'
|
||||
NF_LOG_IPV6='/proc/sys/net/netfilter/nf_log/10'
|
||||
|
||||
# Serialize the WAN logging read->compute->set->commit window across
|
||||
# concurrent ubus write-ACL callers (#151): each toggle re-reads the current
|
||||
# concurrent ubus write-ACL callers: each toggle re-reads the current
|
||||
# firewall.<zone>.log bit, computes a target, then uci set + uci commit. Two
|
||||
# concurrent callers could otherwise interleave and last-commit-wins.
|
||||
#
|
||||
@@ -28,7 +28,7 @@ WAN_LOG_LOCK_FILE="${FWLIVE_WAN_LOG_LOCK_FILE:-/etc/fwlive/logging.lock}"
|
||||
WAN_LOG_BASELINE_FILE="${FWLIVE_WAN_LOG_BASELINE_FILE:-/etc/fwlive/wan-log-baseline}"
|
||||
|
||||
# RFC 8259 string escape. Lives here so prerm can source this file
|
||||
# standalone (#222). rpcd sources us and must not redefine this.
|
||||
# standalone. rpcd sources us and must not redefine this.
|
||||
json_escape() {
|
||||
# Escape for JSON string content per RFC 8259 (including remaining C0 controls).
|
||||
# Slurp stdin as one string. Default awk RS is newline — RS="" is
|
||||
@@ -67,8 +67,8 @@ json_escape() {
|
||||
}'
|
||||
}
|
||||
|
||||
# Production lock dir must be owned by euid with no group/other write (#204).
|
||||
# Avoid GNU/BusyBox `stat -c` — stock OpenWrt omits FEATURE_STAT_FORMAT (#232).
|
||||
# Production lock dir must be owned by euid with no group/other write.
|
||||
# Avoid GNU/BusyBox `stat -c` — stock OpenWrt omits FEATURE_STAT_FORMAT.
|
||||
wan_log_lock_dir_safe() {
|
||||
dir="$1"
|
||||
[ -n "$dir" ] || return 1
|
||||
@@ -86,10 +86,10 @@ wan_log_lock_dir_safe() {
|
||||
# Acquire the exclusive logging lock on fd 9. Blocks until free; fails closed
|
||||
# (return 1) only if the lock file cannot be opened or flock is unavailable.
|
||||
# Create/tighten the lock to 0600 so unprivileged UIDs cannot take LOCK_EX on
|
||||
# a world-readable fd (issue #167 / flock(2) allows exclusive locks on O_RDONLY).
|
||||
# a world-readable fd (flock(2) allows exclusive locks on O_RDONLY).
|
||||
acquire_wan_log_lock() {
|
||||
# Fail closed on symlinks: chmod/chown/exec O_TRUNC follow the target as root
|
||||
# (#204). Default lock lives under /etc/fwlive (root-only), not world-writable
|
||||
# Default lock lives under /etc/fwlive (root-only), not world-writable
|
||||
# /var/lock. Re-check after create/tighten (TOCTOU).
|
||||
lock_dir="$(dirname "$WAN_LOG_LOCK_FILE")"
|
||||
[ -L "$lock_dir" ] && return 1
|
||||
@@ -107,7 +107,7 @@ acquire_wan_log_lock() {
|
||||
# Probe in a subshell first: a failed `exec` redirection aborts a POSIX
|
||||
# non-interactive shell outright, so `|| return 1` on the real exec would
|
||||
# never run — and `2>/dev/null` on the same exec would permanently
|
||||
# silence this process's stderr on the success path (#244).
|
||||
# silence this process's stderr on the success path.
|
||||
( exec 9>>"$WAN_LOG_LOCK_FILE" ) 2>/dev/null || return 1
|
||||
exec 9>>"$WAN_LOG_LOCK_FILE"
|
||||
flock 9 2>/dev/null || {
|
||||
@@ -124,16 +124,16 @@ release_wan_log_lock() {
|
||||
|
||||
find_wan_zone_section() {
|
||||
# Match anonymous (@zone[N]) and named (e.g. wan) sections whose name option
|
||||
# is 'wan'. Prefer the first section whose type is zone (issue #168); skip
|
||||
# is 'wan'. Prefer the first section whose type is zone; skip
|
||||
# non-zone sections that happen to share name='wan'.
|
||||
# uci missing / no wan zone is empty, not fatal. pipefail + set -e
|
||||
# cannot apply to this pipeline (#291 C3).
|
||||
# cannot apply to this pipeline.
|
||||
_zones=$(uci -q show firewall 2>/dev/null \
|
||||
| sed -n "s/^firewall\.\([^.]*\)\.name='wan'$/\1/p") || true
|
||||
for zone in $_zones; do
|
||||
[ -n "$zone" ] || continue
|
||||
# uci -q get exits 1 on a missing section. Capture with || true so
|
||||
# set -e cannot abort inside "$(…)" before || continue (#291 C3).
|
||||
# set -e cannot abort inside "$(…)" before || continue.
|
||||
_type=$(uci -q get "firewall.${zone}" 2>/dev/null || true)
|
||||
[ "$_type" = "zone" ] || continue
|
||||
printf '%s' "$zone"
|
||||
@@ -143,7 +143,7 @@ find_wan_zone_section() {
|
||||
}
|
||||
|
||||
firewall_changes_pending() {
|
||||
# uci miss is "no pending changes". set -e cannot apply (#291 C3).
|
||||
# uci miss is "no pending changes". set -e cannot apply.
|
||||
pending="$(uci -q changes firewall 2>/dev/null || true)"
|
||||
[ -n "$pending" ]
|
||||
}
|
||||
@@ -151,7 +151,7 @@ firewall_changes_pending() {
|
||||
wan_zone_log_value() {
|
||||
zone="$1"
|
||||
[ -n "$zone" ] || return 1
|
||||
# Unset option is a valid empty value; uci -q get exits 1 (#291 C3).
|
||||
# Unset option is a valid empty value; uci -q get exits 1.
|
||||
uci -q get "firewall.${zone}.log" 2>/dev/null || true
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ uci_canonical_firewall_section() {
|
||||
| sed -n '1s/^firewall\.\([^=.]*\)=.*/\1/p'
|
||||
}
|
||||
|
||||
# True when two firewall section ids refer to the same WAN zone (issue #239).
|
||||
# True when two firewall section ids refer to the same WAN zone.
|
||||
# Compare canonical cfg ids — do NOT treat every name=wan zone as identical
|
||||
# (duplicate wan sections would under-match foreign .log deltas as ours).
|
||||
wan_firewall_zone_same() {
|
||||
@@ -196,7 +196,7 @@ wan_log_staged_line_section() {
|
||||
esac
|
||||
}
|
||||
|
||||
# Zone ids that may appear in `uci changes` for firewall.<id>.log (issue #239).
|
||||
# Zone ids that may appear in `uci changes` for firewall.<id>.log.
|
||||
wan_log_staged_zone_ids() {
|
||||
_zone="$1"
|
||||
_staged="$2"
|
||||
@@ -296,7 +296,7 @@ maybe_snapshot_wan_log_baseline() {
|
||||
restore_wan_log_baseline() {
|
||||
path="$(wan_log_baseline_path)"
|
||||
[ -f "$path" ] || return 0
|
||||
# Empty file is a valid "option was unset" baseline (#291 C3).
|
||||
# Empty file is a valid "option was unset" baseline.
|
||||
baseline=$(cat "$path" 2>/dev/null || true)
|
||||
zone=$(find_wan_zone_section)
|
||||
if [ -z "$zone" ]; then
|
||||
@@ -417,18 +417,18 @@ collect_logging_blockers() {
|
||||
check_nf_log_ipv6 || logging_blockers_append 'nf_log_ipv6_missing'
|
||||
|
||||
# Report via LOGGING_BLOCKERS, not exit status: return 1 would abort
|
||||
# build_logging_status_json under set -e (#291 C3).
|
||||
# build_logging_status_json under set -e.
|
||||
return 0
|
||||
}
|
||||
|
||||
collect_logging_warnings() {
|
||||
LOGGING_WARNINGS=''
|
||||
|
||||
# rpcd/fwlive run_with_timeout fail-closes to 127 without timeout (#229).
|
||||
# rpcd/fwlive run_with_timeout fail-closes to 127 without timeout.
|
||||
# Warnings are diagnostics only — do not gate the enable-logging CTA.
|
||||
command -v timeout >/dev/null 2>&1 || logging_warnings_append 'timeout_missing'
|
||||
|
||||
# Report via LOGGING_WARNINGS, not exit status (#291 C3).
|
||||
# Report via LOGGING_WARNINGS, not exit status.
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -444,9 +444,9 @@ json_null_or_string() {
|
||||
|
||||
build_logging_status_json() {
|
||||
zone=$(find_wan_zone_section)
|
||||
# Empty zone / unset log bit are valid; set -e cannot apply (#291 C3).
|
||||
# Empty zone / unset log bit are valid; set -e cannot apply.
|
||||
log_val=$(wan_zone_log_value "$zone") || log_val=
|
||||
# Unset log_limit is a valid empty value; uci -q get exits 1 (#291 C3).
|
||||
# Unset log_limit is a valid empty value; uci -q get exits 1.
|
||||
limit_val=
|
||||
if [ -n "$zone" ]; then
|
||||
limit_val=$(uci -q get "firewall.${zone}.log_limit" 2>/dev/null || true)
|
||||
@@ -491,7 +491,7 @@ restore_wan_zone_log() {
|
||||
zone="$1"
|
||||
previous="$2"
|
||||
[ -n "$zone" ] || return 1
|
||||
# Refuse to publish unrelated staged firewall deltas (issue #168).
|
||||
# Refuse to publish unrelated staged firewall deltas.
|
||||
if firewall_changes_pending; then
|
||||
logger -t fwlive "WAN log rollback skipped: firewall changes pending" 2>/dev/null || true
|
||||
return 1
|
||||
@@ -508,7 +508,7 @@ restore_wan_zone_log() {
|
||||
# closes the read->compute->set->commit window so a concurrent toggle cannot
|
||||
# commit between our read and our write (no lost update / no stale overwrite).
|
||||
#
|
||||
# TOCTOU hardening (#191): UCI staging is global per config file, so a
|
||||
# TOCTOU hardening: UCI staging is global per config file, so a
|
||||
# non-cooperating writer (another admin's `uci set`, the LuCI firewall page)
|
||||
# can stage a delta AFTER the toggle's early firewall_changes_pending check.
|
||||
# Staging and committing therefore live INSIDE this function — the only path
|
||||
@@ -544,7 +544,7 @@ commit_wan_log_change() {
|
||||
zone_json="$2"
|
||||
target="$3"
|
||||
|
||||
# Last-moment guard (#191): runs before OUR delta is staged, so a
|
||||
# Last-moment guard: runs before OUR delta is staged, so a
|
||||
# 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
|
||||
@@ -568,7 +568,7 @@ commit_wan_log_change() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Post-stage guard (#191): anything besides our log option is foreign.
|
||||
# Post-stage guard: anything besides our log option is foreign.
|
||||
# Undo our staging only (set previous / delete to match committed); leave
|
||||
# foreign deltas untouched and abort without commit.
|
||||
_staged=$(uci -q changes firewall 2>/dev/null || true)
|
||||
@@ -589,8 +589,8 @@ commit_wan_log_change() {
|
||||
# firewall_changes_pending from this package's own orphaned write —
|
||||
# but ONLY when nothing foreign is staged: `uci revert firewall` is
|
||||
# config-wide (uci has no option-level revert), and reverting would
|
||||
# clobber a concurrent writer's uncommitted delta (CodeRabbit/luna
|
||||
# fold, #191). With foreign staging present, leave it and warn.
|
||||
# clobber a concurrent writer's uncommitted delta. With foreign
|
||||
# staging present, leave it and warn.
|
||||
_staged=$(uci -q changes firewall 2>/dev/null || true)
|
||||
_total=$(printf '%s\n' "$_staged" | grep -c . 2>/dev/null || true)
|
||||
_ours=$(wan_log_count_our_staged_lines "$zone" "$_staged")
|
||||
@@ -603,7 +603,7 @@ commit_wan_log_change() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Post-commit verification (#191): confirm the committed config really
|
||||
# Post-commit verification: confirm the committed config really
|
||||
# carries what we wrote (empty target = option must now be gone/empty).
|
||||
# A mismatch means another writer overtook the commit: warn loudly but do
|
||||
# NOT blind-revert (that would destroy unrelated committed data); the
|
||||
@@ -619,7 +619,7 @@ commit_wan_log_change() {
|
||||
# would block a concurrent toggle until the holder exits — BusyBox flock has
|
||||
# no -w timeout).
|
||||
#
|
||||
# The ROLLBACK re-acquires the lock (luna fold 2026-08-10): read->compare->
|
||||
# The ROLLBACK re-acquires the lock: read->compare->
|
||||
# restore is only atomic when no other writer can commit between the read and
|
||||
# the restore. All writers hold the same flock, so re-acquiring it makes the
|
||||
# decision-and-restore a serialized unit. The lock is held only for the few
|
||||
@@ -686,7 +686,7 @@ enable_wan_logging() {
|
||||
# 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 (#191): a foreign writer racing between
|
||||
# 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
|
||||
@@ -740,7 +740,7 @@ disable_wan_logging() {
|
||||
# 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 (#191): a foreign writer racing between
|
||||
# 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
|
||||
|
||||
@@ -23,7 +23,7 @@ ADAPTIVE_SH="$LIBEXEC_DIR/fwlive-adaptive-cap.sh"
|
||||
RESOLVE_MAX=32
|
||||
# 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
|
||||
# degrade to no timeout.. The wall-clock budget
|
||||
# below is the real whole-call bound; this per-lookup cap only prevents a
|
||||
# single hung resolver from dominating the budget. LAN/local-resolver
|
||||
# lookups are single-digit ms, so 1s is ample. Passed as argv to
|
||||
@@ -42,7 +42,7 @@ NFT_TIMEOUT=5
|
||||
# CLI-only iptables fixture path for __rulesmap_iptables (no arbitrary paths).
|
||||
RULESMAP_IPTABLES_FILE='/tmp/rulesmap'
|
||||
|
||||
# json_escape lives in fwlive-logging.sh so prerm can source that file alone (#222).
|
||||
# json_escape lives in fwlive-logging.sh so prerm can source that file alone.
|
||||
# shellcheck disable=SC1090,SC1091 # LOGGING_SH resolves to sibling ../fwlive-logging.sh
|
||||
. "$LOGGING_SH"
|
||||
# shellcheck disable=SC1090,SC1091
|
||||
@@ -52,10 +52,10 @@ slug_key() {
|
||||
echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' _' '--'
|
||||
}
|
||||
|
||||
# Cap rules JSON size so a huge ruleset cannot unbound an rpcd reply (#229).
|
||||
# Cap rules JSON size so a huge ruleset cannot unbound an rpcd reply.
|
||||
RULES_MAP_MAX_KEYS=512
|
||||
RULES_MAP_MAX_BYTES=65536
|
||||
# Same wall-clock bound as nft for iptables-save / ip6tables-save (#229).
|
||||
# Same wall-clock bound as nft for iptables-save / ip6tables-save.
|
||||
# Overridable for host tests (e.g. FWLIVE_IPTABLES_TIMEOUT=1).
|
||||
IPTABLES_TIMEOUT="${FWLIVE_IPTABLES_TIMEOUT:-5}"
|
||||
|
||||
@@ -70,7 +70,7 @@ map_add() {
|
||||
# ("foo" vs "foobar") and value false positives ("see foo").
|
||||
# Quoting "$esc_key" makes the match literal (no glob escaping needed).
|
||||
# Callers apply UCI, then labeled (!fw4:) prefixes, then unlabeled
|
||||
# cosmetics so first-wins prefers authoritative names (#230).
|
||||
# cosmetics so first-wins prefers authoritative names.
|
||||
case "$OUT" in
|
||||
*"\"$esc_key\":"*) return 0 ;;
|
||||
esac
|
||||
@@ -92,7 +92,7 @@ map_add() {
|
||||
|
||||
uci_rule_names() {
|
||||
# uci missing / empty firewall config is "no names", not fatal.
|
||||
# pipefail + set -e cannot apply to this pipeline (#291 C3).
|
||||
# pipefail + set -e cannot apply to this pipeline.
|
||||
uci -q show firewall 2>/dev/null | sed -n "s/^firewall\.@rule\[[0-9]*\]\.name='\(.*\)'$/\1/p" || true
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ run_with_timeout() {
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
timeout "$secs" "$@" 2>/dev/null
|
||||
else
|
||||
# Fail closed (#229): never run unbounded when timeout is absent.
|
||||
# Fail closed: never run unbounded when timeout is absent.
|
||||
return 127
|
||||
fi
|
||||
}
|
||||
@@ -170,7 +170,7 @@ map_uci_rule_names() {
|
||||
# Line-wise (here-doc, not an unquoted for-loop) so a name with
|
||||
# spaces stays one token. Names that fail is_uci_style_name are
|
||||
# skipped: parseRuleHint() cannot match them, and fragments of
|
||||
# "My Rule" must not shadow a real rule named Rule (#226).
|
||||
# "My Rule" must not shadow a real rule named Rule.
|
||||
# The while-read stays in the current shell (here-doc, not a
|
||||
# pipeline) so map_add mutations reach OUT.
|
||||
while IFS= read -r name || [ -n "$name" ]; do
|
||||
@@ -187,7 +187,7 @@ EOF
|
||||
}
|
||||
|
||||
# pass=labeled|unlabeled|all — labeled before unlabeled so !fw4: beats
|
||||
# an earlier cosmetic for the same prefix under first-wins (#230).
|
||||
# an earlier cosmetic for the same prefix under first-wins.
|
||||
map_from_nft_stream() {
|
||||
pass="${1:-all}"
|
||||
while IFS= read -r line; do
|
||||
@@ -255,7 +255,7 @@ _fwlive_tmp_dir_ok() {
|
||||
# Fail closed unless the dump dir is a real directory with the sticky
|
||||
# bit. Without sticky, another uid can unlink a just-created mktemp
|
||||
# file and replace it with a symlink before we reopen with `>` as
|
||||
# root (same class as #204). Optional $1 is for CLI tests; production
|
||||
# root (same class as the WAN logging lock). Optional $1 is for CLI tests; production
|
||||
# always uses /tmp.
|
||||
dir="${1:-/tmp}"
|
||||
[ -n "$dir" ] || return 1
|
||||
@@ -288,7 +288,7 @@ build_rules_map() {
|
||||
backend=$(detect_firewall_backend)
|
||||
|
||||
# UCI first, then labeled stream prefixes, then unlabeled cosmetics
|
||||
# (#230). First-wins keeps UCI over nft and !fw4: over cosmetic.
|
||||
# First-wins keeps UCI over nft and !fw4: over cosmetic.
|
||||
map_uci_rule_names
|
||||
|
||||
case "$backend" in
|
||||
@@ -435,16 +435,16 @@ fetch_firewall_logs() {
|
||||
shed_flag=${2:-0}
|
||||
# Adaptive plan prints: lines shed bucket
|
||||
start_cs=$(fwlive_adaptive_clock_cs)
|
||||
# Drop dead oneshot flag — only consumed on logd's stream branch (#306).
|
||||
# Drop dead oneshot flag — only consumed on logd's stream branch.
|
||||
if ! raw=$(ubus call log read "{\"lines\":$lines,\"stream\":false}" 2>/dev/null); then
|
||||
# Do not record ~0 ms as cold: failure is not healthy fast processing
|
||||
# and must not clear an existing hot/shed cap (#329 Hermes Q1).
|
||||
# and must not clear an existing hot/shed cap.
|
||||
out='{"log":[],"error":"log_read_failed"}'
|
||||
fwlive_adaptive_merge_reply "$out" "$shed_flag" "$lines" 0 0
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Do not swallow a filter failure into a silent empty table (#220).
|
||||
# Do not swallow a filter failure into a silent empty table.
|
||||
# If the filter exits non-zero but still printed JSON, keep it.
|
||||
# Empty success is not valid poll JSON — the shipped filter always
|
||||
# prints {"log":[...]}, but a stub/regression must not emit a blank body.
|
||||
@@ -455,12 +455,12 @@ fetch_firewall_logs() {
|
||||
fi
|
||||
[ -n "$out" ] || out='{"log":[],"error":"filter_empty"}'
|
||||
end_cs=$(fwlive_adaptive_clock_cs)
|
||||
# Uptime wrap / skew: end < start — do not record as cold (Grok #329 P2).
|
||||
# Uptime wrap / skew: end < start — do not record as cold.
|
||||
if [ "$end_cs" -ge "$start_cs" ]; then
|
||||
_dur=$(((end_cs - start_cs) * 10))
|
||||
fwlive_adaptive_record "$_dur" "$lines"
|
||||
fi
|
||||
# Layer 1: messages_received stays 0 — no post-filter ash count (Grok #329 P1).
|
||||
# Layer 1: messages_received stays 0 — no post-filter ash count.
|
||||
_trunc=0
|
||||
[ "$shed_flag" = 1 ] && _trunc=1
|
||||
[ "$lines" -lt "$requested" ] && _trunc=1
|
||||
@@ -502,7 +502,7 @@ resolve_hostname() {
|
||||
[ -n "$ip" ] || return 1
|
||||
is_resolvable_address "$ip" || return 1
|
||||
command -v nslookup >/dev/null 2>&1 || return 1
|
||||
# timeout/nslookup miss is "no PTR", not a plugin abort (#291 C3).
|
||||
# timeout/nslookup miss is "no PTR", not a plugin abort.
|
||||
out=$(run_with_timeout "$RESOLVE_TIMEOUT" nslookup "$ip") || return 1
|
||||
[ -n "$out" ] || return 1
|
||||
name=$(parse_nslookup_name "$out") || return 1
|
||||
@@ -510,7 +510,7 @@ resolve_hostname() {
|
||||
printf '%s' "$name"
|
||||
}
|
||||
|
||||
# Strict IPv4/IPv6 validation before nslookup (issue #190): the old char-class
|
||||
# Strict IPv4/IPv6 validation before nslookup: the old char-class
|
||||
# check admitted hostname-shaped tokens of hex chars + dots/colons
|
||||
# (e.g. "dead.beef.cafe.baad"), letting the resolver fall back to upstream DNS
|
||||
# queries chosen by an authenticated session. Validate the real address
|
||||
@@ -523,7 +523,7 @@ is_resolvable_address() {
|
||||
# Reject anything outside the address alphabet BEFORE awk, so embedded
|
||||
# newlines/whitespace (which awk would treat as record separators) can
|
||||
# never let a valid first record mask trailing garbage that still reaches
|
||||
# nslookup (#190, CodeRabbit/luna fold): nslookup only ever sees a single
|
||||
# nslookup: nslookup only ever sees a single
|
||||
# token. Note the check intentionally allows only hex/colon/dot.
|
||||
case "$addr" in
|
||||
*[!0-9a-fA-F:.]*) return 1 ;;
|
||||
@@ -596,7 +596,7 @@ is_resolvable_address() {
|
||||
}
|
||||
|
||||
resolve_addresses() {
|
||||
# Missing resolver must not look like "no PTR" (#218). Check before
|
||||
# Missing resolver must not look like "no PTR". Check before
|
||||
# starting the budget clock or reading stdin.
|
||||
if ! command -v nslookup >/dev/null 2>&1; then
|
||||
command -v logger >/dev/null 2>&1 && logger -t fwlive "nslookup not found; resolve disabled"
|
||||
@@ -604,14 +604,14 @@ resolve_addresses() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Load shed: previous poll was hot (#306 Layer 1).
|
||||
# Load shed: previous poll was hot.
|
||||
if fwlive_adaptive_is_hot; then
|
||||
printf '{"names":{},"disabled":"load"}'
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Wall-clock budget clock starts at the TRUE function entry (luna
|
||||
# folds 2026-08-10): the budget must bound the whole call — including
|
||||
# Wall-clock budget clock starts at the TRUE function entry: the
|
||||
# budget must bound the whole call — including
|
||||
# read_rpc_input (stalled stdin must not escape the budget) and json
|
||||
# parsing. `date +%s` can jump under NTP sync — accepted (worst case
|
||||
# the budget over- or under-runs by the jump; worker starvation is
|
||||
@@ -657,12 +657,12 @@ resolve_addresses() {
|
||||
# never START a lookup past the budget.
|
||||
now=$(date +%s)
|
||||
[ $((now - start)) -ge "$RESOLVE_BUDGET" ] && break
|
||||
# Missing/empty element: set -u cannot read bare $ip (#291 C3).
|
||||
# Missing/empty element: set -u cannot read bare $ip.
|
||||
if ! is_resolvable_address "${ip:-}"; then
|
||||
continue
|
||||
fi
|
||||
# NXDOMAIN / timeout: resolve_hostname returns 1. set -e
|
||||
# cannot apply to this assignment (#291 C3).
|
||||
# cannot apply to this assignment.
|
||||
name=$(resolve_hostname "$ip") || name=
|
||||
if [ -n "$name" ]; then
|
||||
map_add "$ip" "$name"
|
||||
@@ -729,7 +729,7 @@ run_selftest() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Issue #190: hostname-shaped tokens of hex chars + dots/colons passed the
|
||||
# hostname-shaped tokens of hex chars + dots/colons passed the
|
||||
# old char-class check and made the resolver fall back to upstream DNS. Each case
|
||||
# below must REJECT (and would be ACCEPTED by the reverted char-class code).
|
||||
if is_resolvable_address 'ab.cd.ef.01'; then
|
||||
@@ -757,7 +757,7 @@ run_selftest() {
|
||||
echo 'is_resolvable_address: expected accept IPv6-mapped ::ffff:192.0.2.1' >&2
|
||||
return 1
|
||||
fi
|
||||
# Multi-line input must be rejected wholesale (CodeRabbit/luna fold): awk
|
||||
# Multi-line input must be rejected wholesale: awk
|
||||
# reads newline-separated records, so a valid first record must not mask
|
||||
# trailing garbage that would still reach nslookup.
|
||||
if is_resolvable_address "$(printf '192.0.2.1\nnot-an-address')"; then
|
||||
@@ -849,7 +849,7 @@ Address: 127.0.0.1:53")
|
||||
|
||||
run_logging_selftest || return 1
|
||||
|
||||
# Strict-mode smoke (#291 C3): status JSON uses soft-fail helpers
|
||||
# Strict-mode smoke: status JSON uses soft-fail helpers
|
||||
# (uci miss, empty WAN zone). Must not abort under dash/ash set -e.
|
||||
_status=$(build_logging_status_json) || {
|
||||
echo "build_logging_status_json: aborted under set -e" >&2
|
||||
@@ -950,7 +950,7 @@ case "${1:-}" in
|
||||
build_rules_map
|
||||
;;
|
||||
poll)
|
||||
# rpcd may omit argv $3 and pass JSON on stdin (#291 C3).
|
||||
# rpcd may omit argv $3 and pass JSON on stdin.
|
||||
poll_logs "${3:-}"
|
||||
;;
|
||||
resolve)
|
||||
|
||||
Reference in new issue
Block a user