🐤 Sync 2026-09-07 02:18:00

This commit is contained in:
github-actions[bot] committed 2026-09-07 02:18:00 +08:00
1 parent 65e584daaa
commit 4b545cbffa
30 files changed
+1911 -1238

No files matched your search

@@ -4,6 +4,7 @@
# GENERATED FILE — do not edit. Run: ./scripts/gen-all.sh
# source: core/fwlive-log.js CLASSIFY_SPEC
# Shared isFirewallEvent parity logic (shell). Sourced by fwlive-log-filter.sh and tests.
# Sourced library: do not add set -euo here (callers own strict mode, #291 C3).
# One awk process classifies a batch (MODE=json) or one message (default).
_fwlive_run_classify() {
@@ -8,6 +8,14 @@
#
# Perf (#219): 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 -euo itself (#291 C3).
set -eu
# pipefail: BusyBox ash supports it; Debian dash (host `sh`) rejects a
# bare `set -o pipefail`. Probe in a subshell (same class as #244).
# shellcheck disable=SC3040 # pipefail is ash/bash; dash probe is a subshell
(set -o pipefail) 2>/dev/null && set -o pipefail
if ! command -v jsonfilter >/dev/null 2>&1; then
command -v logger >/dev/null 2>&1 && logger -t fwlive "jsonfilter not found; cannot filter firewall logs"
@@ -26,5 +34,7 @@ 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).
printf '%s' "$input" | jsonfilter -e '@.log[*]' 2>/dev/null | _fwlive_filter_json_entries
# jsonfilter miss / empty @.log is not fatal; must still close JSON (#220).
# set -e + pipefail cannot apply to this pipeline (#291 C3).
printf '%s' "$input" | jsonfilter -e '@.log[*]' 2>/dev/null | _fwlive_filter_json_entries || true
printf '%s' ']}'
@@ -3,6 +3,11 @@
# Copyright 2025-2026 Lucas Albers <lucas.b.albers@gmail.com>
#
# WAN zone logging helpers for ubus fwlive (logging_status / enable / disable).
#
# 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).
NF_LOG_IPV4='/proc/sys/net/netfilter/nf_log/2'
NF_LOG_IPV6='/proc/sys/net/netfilter/nf_log/10'
@@ -121,11 +126,16 @@ 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
# 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).
_zones=$(uci -q show firewall 2>/dev/null \
| sed -n "s/^firewall\.\([^.]*\)\.name='wan'$/\1/p")
| sed -n "s/^firewall\.\([^.]*\)\.name='wan'$/\1/p") || true
for zone in $_zones; do
[ -n "$zone" ] || continue
[ "$(uci -q get "firewall.${zone}" 2>/dev/null)" = "zone" ] || continue
# uci -q get exits 1 on a missing section. Capture with || true so
# set -e cannot abort inside "$(…)" before || continue (#291 C3).
_type=$(uci -q get "firewall.${zone}" 2>/dev/null || true)
[ "$_type" = "zone" ] || continue
printf '%s' "$zone"
return 0
done
@@ -133,14 +143,16 @@ find_wan_zone_section() {
}
firewall_changes_pending() {
pending="$(uci -q changes firewall 2>/dev/null)"
# uci miss is "no pending changes". set -e cannot apply (#291 C3).
pending="$(uci -q changes firewall 2>/dev/null || true)"
[ -n "$pending" ]
}
wan_zone_log_value() {
zone="$1"
[ -n "$zone" ] || return 1
uci -q get "firewall.${zone}.log" 2>/dev/null
# Unset option is a valid empty value; uci -q get exits 1 (#291 C3).
uci -q get "firewall.${zone}.log" 2>/dev/null || true
}
# Resolve a firewall section id to its canonical cfgXXXX form (issue B-1 /
@@ -284,7 +296,8 @@ maybe_snapshot_wan_log_baseline() {
restore_wan_log_baseline() {
path="$(wan_log_baseline_path)"
[ -f "$path" ] || return 0
baseline=$(cat "$path" 2>/dev/null)
# Empty file is a valid "option was unset" baseline (#291 C3).
baseline=$(cat "$path" 2>/dev/null || true)
zone=$(find_wan_zone_section)
if [ -z "$zone" ]; then
logger -t fwlive "WAN log baseline restore skipped: no WAN zone" 2>/dev/null || true
@@ -363,7 +376,7 @@ wan_filter_log_clear_value() {
read_nf_log_backend() {
path="$1"
[ -f "$path" ] || return 1
val=$(cat "$path" 2>/dev/null)
val=$(cat "$path" 2>/dev/null) || return 1
[ -n "$val" ] && [ "$val" != 'none' ]
}
@@ -403,8 +416,9 @@ collect_logging_blockers() {
check_nf_log_ipv4 || logging_blockers_append 'nf_log_ipv4_missing'
check_nf_log_ipv6 || logging_blockers_append 'nf_log_ipv6_missing'
[ -n "$LOGGING_BLOCKERS" ] || return 0
return 1
# Report via LOGGING_BLOCKERS, not exit status: return 1 would abort
# build_logging_status_json under set -e (#291 C3).
return 0
}
collect_logging_warnings() {
@@ -414,8 +428,8 @@ collect_logging_warnings() {
# Warnings are diagnostics only — do not gate the enable-logging CTA.
command -v timeout >/dev/null 2>&1 || logging_warnings_append 'timeout_missing'
[ -n "$LOGGING_WARNINGS" ] || return 0
return 1
# Report via LOGGING_WARNINGS, not exit status (#291 C3).
return 0
}
json_null_or_string() {
@@ -430,8 +444,13 @@ json_null_or_string() {
build_logging_status_json() {
zone=$(find_wan_zone_section)
log_val=$(wan_zone_log_value "$zone")
limit_val=$( [ -n "$zone" ] && uci -q get "firewall.${zone}.log_limit" 2>/dev/null )
# Empty zone / unset log bit are valid; set -e cannot apply (#291 C3).
log_val=$(wan_zone_log_value "$zone") || log_val=
# Unset log_limit is a valid empty value; uci -q get exits 1 (#291 C3).
limit_val=
if [ -n "$zone" ]; then
limit_val=$(uci -q get "firewall.${zone}.log_limit" 2>/dev/null || true)
fi
wan_log=false
if wan_filter_log_enabled "$log_val"; then
wan_log=true
+44 -14
View File
@@ -8,6 +8,17 @@
# resolve — reverse DNS for IP addresses (BusyBox nslookup)
# logging_status — WAN zone logging readiness (no args)
# enable_wan_logging / disable_wan_logging — opt-in WAN zone log=1 (no args)
#
# Entry point (rpcd plugin). Sourced helpers inherit these options. Do not
# copy set -euo into fwlive-logging.sh — prerm sources that file alone (#222)
# and keeps soft-fail / || return 1 contracts (#244, #291 C3).
set -eu
# pipefail: BusyBox ash (device) supports it; Debian dash (host `sh` in
# CI) rejects `set -o pipefail` as a startup abort. Probe in a subshell —
# a failed `set` in-process is fatal even under `if` (same class as #244
# failed exec).
# shellcheck disable=SC3040 # pipefail is ash/bash; dash probe is a subshell
(set -o pipefail) 2>/dev/null && set -o pipefail
FW4_TAG='!fw4: '
LIBEXEC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
@@ -83,7 +94,9 @@ map_add() {
}
uci_rule_names() {
uci -q show firewall 2>/dev/null | sed -n "s/^firewall\.@rule\[[0-9]*\]\.name='\(.*\)'$/\1/p"
# uci missing / empty firewall config is "no names", not fatal.
# pipefail + set -e cannot apply to this pipeline (#291 C3).
uci -q show firewall 2>/dev/null | sed -n "s/^firewall\.@rule\[[0-9]*\]\.name='\(.*\)'$/\1/p" || true
}
is_uci_style_name() {
@@ -396,7 +409,8 @@ poll_lines_from_input() {
json_get_var first 1
json_select ..
# shellcheck disable=SC2154 # set by json_get_var above
case "$first" in
# Missing addresses[1] leaves first unset; set -u (#291 C3).
case "${first:-}" in
''|*[!0-9]*) ;;
*) lines="$first" ;;
esac
@@ -462,9 +476,10 @@ resolve_hostname() {
[ -n "$ip" ] || return 1
is_resolvable_address "$ip" || return 1
command -v nslookup >/dev/null 2>&1 || return 1
out=$(run_with_timeout "$RESOLVE_TIMEOUT" nslookup "$ip")
# timeout/nslookup miss is "no PTR", not a plugin abort (#291 C3).
out=$(run_with_timeout "$RESOLVE_TIMEOUT" nslookup "$ip") || return 1
[ -n "$out" ] || return 1
name=$(parse_nslookup_name "$out")
name=$(parse_nslookup_name "$out") || return 1
[ -n "$name" ] || return 1
printf '%s' "$name"
}
@@ -598,11 +613,14 @@ resolve_addresses() {
now=$(date +%s)
[ $((now - start)) -ge "$RESOLVE_BUDGET" ] && break
json_get_var ip "$idx"
if ! is_resolvable_address "$ip"; then
# Missing/empty element: set -u cannot read bare $ip (#291 C3).
if ! is_resolvable_address "${ip:-}"; then
idx=$((idx + 1))
continue
fi
name=$(resolve_hostname "$ip")
# NXDOMAIN / timeout: resolve_hostname returns 1. set -e
# cannot apply to this assignment (#291 C3).
name=$(resolve_hostname "$ip") || name=
if [ -n "$name" ]; then
map_add "$ip" "$name"
count=$((count + 1))
@@ -789,6 +807,17 @@ Address: 127.0.0.1:53")
run_logging_selftest || return 1
# Strict-mode smoke (#291 C3): 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
return 1
}
case "$_status" in
*'"blockers":'*) ;;
*) echo "build_logging_status_json: missing blockers: $_status" >&2; return 1 ;;
esac
if ! command -v jshn >/dev/null 2>&1; then
echo "skip: jshn not available (poll cap via jshn not tested)" >&2
return 0
@@ -834,7 +863,7 @@ Address: 127.0.0.1:53")
return 0
}
case "$1" in
case "${1:-}" in
__selftest)
run_selftest
exit $?
@@ -845,22 +874,22 @@ case "$1" in
exit $?
;;
__poll_clamp)
poll_clamp_lines "$2"
poll_clamp_lines "${2:-}"
exit $?
;;
__tmp_dir_ok)
_fwlive_tmp_dir_ok "$2"
_fwlive_tmp_dir_ok "${2:-}"
exit $?
;;
__resolve_one)
if name=$(resolve_hostname "$2"); then
if name=$(resolve_hostname "${2:-}"); then
printf '%s\n' "$name"
exit 0
fi
exit 1
;;
__parse_nslookup)
name=$(parse_nslookup_name "$2")
name=$(parse_nslookup_name "${2:-}")
if [ -n "$name" ]; then
printf '%s\n' "$name"
exit 0
@@ -874,15 +903,16 @@ case "$1" in
echo '{"rules":{},"poll":{"addresses":[]},"resolve":{"addresses":[]},"logging_status":{},"enable_wan_logging":{},"disable_wan_logging":{}}'
;;
call)
case "$2" in
case "${2:-}" in
rules)
build_rules_map
;;
poll)
poll_logs "$3"
# rpcd may omit argv $3 and pass JSON on stdin (#291 C3).
poll_logs "${3:-}"
;;
resolve)
resolve_addresses "$3"
resolve_addresses "${3:-}"
;;
logging_status)
build_logging_status_json